keyof, typeof & Indexed Access
TypeScript has operators that work on types rather than values. keyof lists an object type's property names, typeof captures the type of an existing variable, and indexed access (T["prop"]) looks up the type of one property. Together they let types stay in sync with your code automatically.
keyof
keyof T produces a union of the property names of T as string literal types:
interface Settings { theme: string; fontSize: number; autosave: boolean; } type SettingName = keyof Settings; const a: SettingName = "fontSize"; const b: SettingName = "colour";
error TS2322: Type '"colour"' is not assignable to type 'keyof Settings'.
SettingName is "theme" | "fontSize" | "autosave". If someone adds a property to Settings, the union updates by itself.
A type-safe property getter
keyof is at its most useful in generics, connecting a key argument to the type of the value it reads:
function get<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const settings = { theme: "dark", fontSize: 14, autosave: true }; const size = get(settings, "fontSize"); const theme = get(settings, "theme"); console.log(size * 2, theme.toUpperCase());
28 DARK
Each call returns the precise type: size is a number, theme is a string.
The type-level typeof
In JavaScript, typeof is a runtime operator that returns a string like "object". In a type position, TypeScript's typeof instead gives you the full static type of a variable:
const defaults = { retries: 3, timeoutMs: 5000, verbose: false, }; type Config = typeof defaults; function start(config: Config) { console.log(`retrying ${config.retries}x, timeout ${config.timeoutMs}ms`); } start({ ...defaults, retries: 5 });
retrying 5x, timeout 5000ms
Config is { retries: number; timeoutMs: number; verbose: boolean }, derived from the object itself. Change defaults and the type follows โ there's no separate interface to keep in sync.
It works on functions too. Combined with the built-in ReturnType (lesson 27), you can name the type a function returns without writing it out:
function createUser(name: string) { return { name, createdAt: new Date(0), roles: ["member"] }; } type User = ReturnType<typeof createUser>; const u: User = createUser("Ola"); console.log(u.roles);
[ 'member' ]
Combining keyof typeof
To get the keys of an object value, first take its type, then its keys:
const icons = { home: "๐ ", search: "๐", settings: "โ๏ธ", }; type IconName = keyof typeof icons; function icon(name: IconName) { return icons[name]; } console.log(icon("search"));
๐
Indexed access types
T["prop"] looks up the type of a property, just like obj["prop"] looks up a value:
interface Order { id: string; customer: { name: string; email: string }; items: { sku: string; qty: number }[]; } type Customer = Order["customer"]; type Item = Order["items"][number]; type IdOrEmail = Order["id" | "customer"]; const line: Item = { sku: "A-1", qty: 2 }; const who: Customer = { name: "Ned", email: "n@x.io" }; console.log(line.qty, who.name);
2 Ned
Indexing an array type with number gives the element type โ that's how Order["items"][number] produces { sku: string; qty: number }. You can also index with a union of keys to get a union of property types.
Turning a constant array into a union
Putting it all together gives a very common idiom: define values once at runtime, and derive the type from them:
const PLANS = ["free", "pro", "team"] as const; type Plan = (typeof PLANS)[number]; function isPlan(value: string): value is Plan { return (PLANS as readonly string[]).includes(value); } console.log(isPlan("pro"), isPlan("enterprise"));
true false
typeof PLANS is readonly ["free", "pro", "team"], and indexing it with number yields "free" | "pro" | "team". The array is available at runtime for validation and dropdown menus; the type is available for checking.