Large codebases repeat themselves when types are written by hand next to the values they describe: an object literal here, an interface for it there, a union of its keys somewhere else. TypeScript's type operators let you derive one from another so there is a single source of truth. This lesson covers keyof, the type-level typeof, and indexed access types, and shows the patterns that combine them.
keyof T produces a union of the property names of T as literal types:
interface Person { name: string; age: number; email?: string }
type PersonKey = keyof Person; // "name" | "age" | "email"
function printField(p: Person, key: PersonKey) {
console.log(key, p[key]);
}
printField({ name: "Ada", age: 36 }, "age"); // OK
printField({ name: "Ada", age: 36 }, "phone"); // ErrorDetails worth knowing:
keyof describes names, not presence.keyof is string | number, because JavaScript converts numeric keys to strings.Extract<keyof T, string> when you need only string keys (for example to build a template literal).JavaScript's typeof returns a string at runtime. In a type position, TypeScript's typeof captures the static type of a variable, property or function:
const defaults = { retries: 3, timeout: 5000, verbose: false };
type Options = typeof defaults; // { retries: number; timeout: number; verbose: boolean }
function merge(overrides: Partial<Options>): Options {
return { ...defaults, ...overrides };
}
type MergeFn = typeof merge; // (overrides: Partial<Options>) => Options
type Result = ReturnType<typeof merge>; // Optionstypeof only works on identifiers and property paths (typeof obj.prop), not on arbitrary expressions such as typeof fn(). Combined with as const, it turns a runtime constant into an exact type, which is how most projects define enums-like lookups without the enum keyword.
T[K] looks up the type of property K on T. The key can be a literal, a union of literals, keyof T, or number for arrays:
interface Order {
id: string;
customer: { name: string; vip: boolean };
lines: { sku: string; qty: number }[];
}
type Customer = Order["customer"]; // { name: string; vip: boolean }
type Line = Order["lines"][number]; // { sku: string; qty: number }
type IdOrCustomer = Order["id" | "customer"]; // string | { name: string; vip: boolean }
type AnyField = Order[keyof Order]; // union of all property types
type Sku = Order["lines"][number]["sku"]; // stringIndexed access is purely a type operation. Order["id"] is a type, so it cannot appear where a value is expected, and the index must be a type as well (Order[typeof key] if key is a runtime constant, not Order[key]).
The operators are designed to chain. Two patterns appear in almost every project.
Union from a runtime array
export const ROLES = ["admin", "editor", "viewer"] as const;
export type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"
function isRole(x: string): x is Role {
return (ROLES as readonly string[]).includes(x);
}The runtime array validates input; the derived type checks the rest of the code. Changing the list updates both.
Type-safe property access
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const order: Order = { id: "o1", customer: { name: "Ada", vip: true }, lines: [] };
getProperty(order, "customer").vip; // boolean
getProperty(order, "total"); // Error: "total" is not a key of OrderThe same idea powers typed event emitters, form field helpers and translation lookups keyed by keyof typeof messages.
| Expression | Produces |
|---|---|
| keyof T | Union of property names of T |
| typeof value (type position) | Static type of a variable, property or function |
| T["prop"] | Type of that property |
| T[keyof T] | Union of all property value types |
| T[number] | Element type of an array or tuple |
| (typeof arr)[number] | Union of a const array's literal values |
| keyof typeof obj | Union of an object value's keys |
typeof on an expression (typeof getUser()); wrap the call in a variable or use ReturnType<typeof getUser>.as const, so typeof ROLES becomes string[] and [number] yields plain string.string where keyof T is required; narrow it with a guard first, or type the parameter as keyof T from the start.What is `keyof { a: number; b?: string }`?
keyof T gives the union of property names; with an index signature it becomes string | number.typeof captures the type of a variable or function; pair it with as const for exact literal types.T[K] reads a property's type; T[number] reads an array's element type; T[keyof T] unions all values.(typeof ARR)[number] derives a union from a runtime list, keeping one source of truth.K extends keyof T with return type T[K] is the foundation of type-safe property access.Next lesson: Utility Types: Partial, Pick, Omit, Record and More — the built-in helpers that transform existing types.