TypeScript · Advanced types
`keyof` & `typeof`
⚡ TL;DR —
keyof Tproduces the union ofT's property keys as a type;typeof valuelifts a runtime value up into the type world. Chained askeyof typeof obj, they let a type track a real object instead of you hand-maintaining a parallel union that drifts.
🧠 Mental model
TypeScript has two worlds: values (things that exist at runtime) and types (things erased at compile time). Most operators live in exactly one. typeof is the bridge upward — value → type. keyof operates within the type world — type → the union of its keys. Neither crosses back down; you can't keyof a value or typeof a type.
The reason to care is drift. If you write a config object and a type Keys = 'a' | 'b' union by hand, they will eventually disagree. keyof typeof config derives the union from the object, so it can never fall out of sync.
⚙️ How it actually works
keyof turns a type into a union of its keys:
type P = { a: number; b: string };
type K = keyof P; // 'a' | 'b'
typeof (in type position) is a type query — distinct from the JavaScript runtime operator that returns "string". It reads the static type of a value in scope:
const config = { host: 'localhost', port: 5432 };
type Config = typeof config; // { host: string; port: number }
The combo derives a union from real data. keyof typeof config = 'host' | 'port'. Add as const and both the keys and the literal values stay narrow, so you can build a value-union too:
const ROLES = ['admin', 'editor', 'viewer'] as const;
type Role = typeof ROLES[number]; // 'admin' | 'editor' | 'viewer'
Watch the index-signature case: keyof { [k: string]: V } is string | number — not string — because JS silently coerces numeric keys to strings, so numeric indexing is always legal. Same for keyof Record<string, V>.
💻 Code
// A getter that's provably safe against typos
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Ada' };
pluck(user, 'name'); // string
// pluck(user, 'nope'); // ❌ 'nope' is not keyof typeof user
// Turn a const object into a single source of truth
const STATUS = { active: 1, archived: 2 } as const;
type StatusKey = keyof typeof STATUS; // 'active' | 'archived'
type StatusCode = typeof STATUS[StatusKey]; // 1 | 2
// keyof an index signature is WIDER than you expect
type Dict = { [k: string]: number };
type DictKeys = keyof Dict; // string | number (not just string!)
⚖️ Trade-offs
keyof typeofover anas constobject is usually better than a TSenum— one source of truth, no runtime enum object, tree-shakeable, and the values stay real literals you can log and serialise.keyofon a type with an index signature givesstring | number, which is often too wide to be useful as an exhaustive key list — reach for a closed object +as constwhen you need exact keys.typeofonly reads values already in scope and never evaluates them — you can'ttypeof someExpression()to get a call result (that'sReturnType);typeof fngives the function type.
💣 Gotchas interviewers probe
- There are two
typeofs. The runtime operator (typeof x === 'string', returns a string, used for narrowing) and the type-query (typeof xin type position, returns a type). Same keyword, opposite worlds — conflating them is a classic tell. keyofof an index-signature type isstring | number, notstring. Numeric-key coercion catches almost everyone.keyof anyisstring | number | symbol— which is exactly the built-inPropertyKey.- Object numeric keys are strings at runtime but
numbertokeyof.{ 0: 'x' }—keyofreports0(a number), yetObject.keysgives['0']. The type world and runtime disagree here. - All of it is erased.
keyof typeof apiResponseis a compile-time claim about a shape; a value parsed from JSON isn't guaranteed to have those keys. If the object comes from outside your program, validate it — the type is documentation, not a guard.
🎯 Say this in the interview
"
keyofgives me the union of a type's keys, andtypeof— in type position — lifts a runtime value into the type world; it's a type query, totally separate from the JavaScripttypeofoperator that returns a string. The pattern I use constantly iskeyof typeof someConstso a key union is derived from the real object and can't drift from it, and withas constI can pull the value union out too viatypeof OBJ[keyof typeof OBJ]. The gotcha I'd flag is thatkeyofon an index signature isstring | number, notstring, because JS coerces numeric keys. And I keep in mind it's all erased — it describes a shape, it doesn't verify one, so untrusted input still needs a runtime check."
🔗 Go deeper
- TS Handbook —
keyoftypes — including the index-signaturestring | numbercase. - TS Handbook —
typeoftypes — the type-query operator and its limits. - TS Handbook — Indexed access types — pairs with
keyofto pull value types out (T[keyof T]).