Chapter 17: TypeScript Keyof
TypeScript like we’re sitting together at a desk, drawing on a notepad, and going through real examples step by step.
keyof is one of the most powerful (and most frequently used) type operators in modern TypeScript. Once you understand it well, many “advanced typing” puzzles suddenly become simple.
1. The absolute simplest explanation first
keyof Type answers the question:
“What are all the possible property names (keys) that this Type allows?”
It returns a union of string literals (or number literals, or symbols in some cases) representing those keys.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
interface User { id: number; name: string; email?: string; isActive: boolean; } type UserKeys = keyof User; // ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ // "id" | "name" | "email" | "isActive" |
That’s it at the basic level.
2. Why is this useful? (the real motivation)
Without keyof, when you want to write a function that accepts a key of an object, you usually end up doing one of these unsafe things:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
// ❌ Unsafe string function getValue(obj: User, key: string) { return obj[key]; // any — lost all type info } // ❌ Repetitive union (breaks when you add new fields) function getValue(obj: User, key: "id" | "name" | "email" | "isActive") { ... } |
With keyof → automatic, safe, refactor-friendly:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; // perfectly typed — returns T[K] } const user: User = { id: 1, name: "Sara", isActive: true }; const name = getValue(user, "name"); // string const active = getValue(user, "isActive"); // boolean // getValue(user, "password"); // Error — not a key! |
This is the classic safe property accessor pattern — you’ll see it everywhere in libraries, tRPC, Zustand selectors, Redux, etc.
3. keyof on different kinds of types (very important differences)
| Type you put into keyof | What keyof returns | Common scenario |
|---|---|---|
| Regular interface / object type | Union of literal keys “a” | “b” | “c” | Most common case |
| Type with string index signature | string (or string | number | symbol) | Dictionary / Record-like objects |
| Type with number index signature | number | Array-like, sparse arrays |
| Array type | “length” | “push” | “pop” | … (all Array methods + index) | Rarely useful directly |
| Tuple type | “0” | “1” | “2” | … | “length” | Accessing known positions safely |
| Enum (numeric) | enum values (0 | 1 | 2 …) | Mapping enum → string |
| typeof someValue | keys of that concrete object | Runtime object → type-safe keys |
Examples:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// string index signature → keyof becomes string type Dictionary = { [key: string]: number }; type DictKeys = keyof Dictionary; // string // number index signature type NumberMap = { [key: number]: string }; type NumKeys = keyof NumberMap; // number // real object (very useful pattern!) const settings = { theme: "dark", fontSize: 16, notifications: true, } as const; type SettingKey = keyof typeof settings; // "theme" | "fontSize" | "notifications" (literal union!) |
4. Most common real-world patterns in 2025–2026 codebases
Pattern 1: Safe property picker / getter (the #1 use case)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> { return keys.reduce((acc, k) => { acc[k] = obj[k]; return acc; }, {} as Pick<T, K>); } const preview = pick(user, "id", "name"); // { id: number; name: string } |
Pattern 2: keyof + typeof for config / constant objects
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
const roles = { admin: "ADMIN", editor: "EDITOR", viewer: "VIEWER", } as const; type Role = keyof typeof roles; // "admin" | "editor" | "viewer" type RoleValue = (typeof roles)[Role]; // "ADMIN" | "EDITOR" | "VIEWER" |
Pattern 3: keyof in mapped types (very powerful when combined)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
type BooleanFlags<T> = { [K in keyof T]: boolean; }; type UserFlags = BooleanFlags<User>; // { // id: boolean; // name: boolean; // email: boolean; // isActive: boolean; // } |
Pattern 4: keyof + template literals (modern — since ~4.1+)
|
0 1 2 3 4 5 6 7 8 9 10 11 |
type EventName = "click" | "hover" | "focus"; type OnEvent = `on${Capitalize<EventName>}`; // "onClick" | "onHover" | "onFocus" type HandlerMap = { [K in OnEvent]: (e: Event) => void }; |
Pattern 5: keyof + generics constraint (protects dynamic keys)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
function updateField<T extends object, K extends keyof T>( obj: T, field: K, value: T[K] ) { obj[field] = value; } |
5. Quick “cheat sheet” table
| You want to… | Use this | Result type example |
|---|---|---|
| Get union of keys of interface | keyof User | “id” | “name” | … |
| Get keys of a real object value | keyof typeof myObject | literal union (best with as const) |
| Constrain generic key parameter | K extends keyof T | Only valid keys allowed |
| Create boolean/object map from keys | { [K in keyof T]: boolean } | Options / flags type |
| Make all keys optional | Partial<Record<keyof T, string>> | Partial update payloads |
| Combine with Pick / Omit | Pick<T, keyof T> (all keys) | Copy type, or Omit<T, “secret”> |
6. Common gotchas / things that surprise people
- keyof on index-signature-heavy types becomes string / number → loses literal safety
- Without as const → keyof typeof obj usually becomes just string
- Symbol keys are included (but rare in practice)
- keyof sees only public members (not #private)
Mini homework — try in playground
- Take any interface you have
- Create type Keys = keyof YourType
- Write a generic get(obj, key: Keys) function
- Create type Flags = { [K in keyof YourType]?: boolean }
- Try keyof typeof someConstantObject with and without as const
Which pattern feels most useful for your current code right now?
Want to go deeper into:
- keyof + mapped types in detail (with remapping as clause)
- keyof vs typeof vs indexed access T[K]
- Real tRPC / Zod / React Hook Form patterns using keyof
- keyof with unions / intersections / conditional types
Just tell me — we continue from exactly where you need it! 😄
