How keyof and typeof work in TypeScript
keyof extracts a type's property names as a string literal union. typeof captures the type of any value at compile time, so you can reference that type without writing it twice.
Theory
TL;DR
keyof Typereturns a union of that type's key names:keyof Persongives"name" | "age"typeof valuereturns the inferred type of a value:typeof persongives{ name: string; age: number }- Think of
keyofas the button panel on a vending machine: it lists valid choices, nothing more.typeofis reading the label on what you already hold - Use
K extends keyof Tto constrain a function parameter to valid keys; usetypeof objto avoid re-declaring an existing shape - Combined as
keyof typeof obj, they give you a key union straight from a value, no separate type declaration needed
Quick example
type Person = {
name: string;
age: number;
};
type Keys = keyof Person; // "name" | "age"
const person = { name: "Alice", age: 30 };
type PersonType = typeof person; // { name: string; age: number }
const key: Keys = "name"; // OK
// const bad: Keys = "email"; // Error: not assignable to "name" | "age"keyof limits key to Person's actual properties. typeof copies person's shape without you writing it out manually.
Key difference
keyof operates on types (interfaces, type aliases, class declarations) and produces a union of their property names. typeof operates on values (variables, expressions, function references) and produces a type you can use anywhere a type is expected. They solve opposite problems: typeof answers "what type does this value have?", keyof answers "what keys does this type expose?". Put them together and you go from a plain object in your code to a safe key union in a single step.
When to use
- Generic property accessor:
K extends keyof Tso thatobj[key]is always type-safe and the return type is exact - Avoid re-declaring a type for an existing object or constant:
type Config = typeof config - Iterate or map over an object's keys:
keyofinside mapped types like{ [K in keyof T]: ... } - Extract prop types from a component or function:
Parameters<typeof Button>[0] - Constrain a string argument to valid property names:
(key: keyof User) => User[key]
Comparison
keyof | typeof | |
|---|---|---|
| Input | A type | A value or expression |
| Output | String literal union of property names | The inferred type of the value |
| Works on | Interfaces, type aliases, classes | Variables, objects, functions, expressions |
| Runtime cost | None | None |
| Common combo | T[K] indexed access | keyof typeof obj |
| Typical use | Constrain key parameters | Reuse existing object shapes |
How the compiler handles this
Both operators are compile-time only. No code is emitted for them at runtime. When TypeScript processes keyof T, it walks the type's property map and builds a string literal union from the public property names. For typeof expr, the type checker resolves the expression using control flow analysis and freezes the result as a type alias. One thing that catches developers: let widens the inferred type. const y = 5 gives typeof y as 5 (a literal type). let y = 5 gives number. TypeScript 4.1+ also includes symbol keys in keyof results, so the union can include string | number | symbol depending on the type.
Common mistakes
Running keyof on a primitive
type S = keyof string;
// number | typeof Symbol.iterator | "toString" | "charAt" | ...
// Gives you all string prototype members, not what you usually want
type OK = keyof { x: string }; // "x"Apply keyof to your own type definitions, not to built-in primitives.
typeof on an uninitialized variable
let x;
type T = typeof x; // any - no useful type information
const y = 5;
type U = typeof y; // 5 (literal type, much more useful)Initialize with const before using typeof on a variable.
keyof typeof on an empty object
let obj = {};
type K = keyof typeof obj; // string | number | symbol
const typed: { a: number } = { a: 1 };
type K2 = keyof typeof typed; // "a"An empty {} carries an implicit index signature in TypeScript. keyof {} resolves to the full index signature type, not never. Use const and provide explicit properties, or annotate the variable.
Forgetting symbol keys in TypeScript 4.1+
// If you want only string keys from keyof:
type StringKeys = Extract<keyof T, string>;Before 4.1, keyof returned string keys only. From 4.1 onward, symbol keys are included too.
Real-world usage
- Zod:
type Schema = z.infer<typeof userSchema>to turn a runtime validator into a TypeScript type - React:
type ButtonProps = Parameters<typeof Button>[0]for component prop types without manual declaration - Redux Toolkit:
type State = ReturnType<typeof store.getState> - Generic utilities:
function get<T, K extends keyof T>(obj: T, key: K): T[K]is the standard safe accessor pattern - Mapped types:
Record<keyof Config, string>to transform all keys of an existing type
Follow-up questions
Q: What does keyof T[K] produce?
A: The keys of whatever type T[K] resolves to. If T = { user: { name: string } } and K = "user", then keyof T[K] is "name".
Q: How does keyof behave on intersection types?
A: keyof (A & B) gives keyof A | keyof B. An intersection type has all properties from both sides, so keyof collects all of them.
Q: Does typeof work on overloaded functions?
A: It captures the full overloaded call signature. To extract specific parts, use Parameters<typeof fn> or ReturnType<typeof fn>.
Q: What changed in TypeScript 4.1 for keyof?
A: Before 4.1, keyof returned only string keys. From 4.1 onward, it includes symbol keys as well, so the result type can be string | number | symbol.
Q (senior): A developer wrote keyof typeof obj where obj = {} is declared with let. They expected never but got string | number | symbol. Why?
A: Empty object {} in TypeScript has an implicit index signature that accepts any string, number, or symbol key. So keyof typeof {} resolves to that full index signature type. To get never, you need a type with no properties, such as Record<never, never>, or an explicitly annotated variable with a closed type.
Examples
Basic: type-safe property getter
type User = { id: number; name: string };
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { id: 1, name: "Bob" };
const name = getProp(user, "name"); // inferred as string
// getProp(user, "email"); // Error: "email" is not in User
console.log(name); // "Bob"K extends keyof T tells TypeScript that key must be an actual property of obj. The return type T[K] then resolves to the exact type for that property. No type assertion, no any, no runtime guard needed.
Intermediate: config object with inferred types
const config = {
host: "localhost",
port: 3000,
debug: true,
} as const;
type Config = typeof config;
// { readonly host: "localhost"; readonly port: 3000; readonly debug: true }
type ConfigKey = keyof Config; // "host" | "port" | "debug"
function readConfig(key: ConfigKey): Config[typeof key] {
return config[key];
}
readConfig("host"); // "localhost"
// readConfig("url"); // Error: "url" is not assignable to ConfigKeyas const preserves literal types instead of widening them. typeof config captures the exact shape, keyof then constrains which keys are valid. I've seen this pattern catch typos in config lookups that would otherwise surface only at runtime.
Advanced: type-safe event system
const handlers = {
userCreated: (id: number) => void 0,
userDeleted: (id: number) => void 0,
pageLoaded: () => void 0,
};
type Handlers = typeof handlers;
type EventName = keyof Handlers; // "userCreated" | "userDeleted" | "pageLoaded"
function emit<K extends EventName>(name: K, ...args: Parameters<Handlers[K]>): void {
(handlers[name] as (...a: unknown[]) => void)(...args);
}
emit("userCreated", 42); // OK
// emit("userCreated"); // Error: expected 1 argument
// emit("unknown", 42); // Error: "unknown" not in EventNamekeyof typeof handlers gives the event name union. Parameters<Handlers[K]> then extracts the argument list for that specific handler. Both the event name and its arguments are fully type-checked at compile time.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.