Skip to main content

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 Type returns a union of that type's key names: keyof Person gives "name" | "age"
  • typeof value returns the inferred type of a value: typeof person gives { name: string; age: number }
  • Think of keyof as the button panel on a vending machine: it lists valid choices, nothing more. typeof is reading the label on what you already hold
  • Use K extends keyof T to constrain a function parameter to valid keys; use typeof obj to 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

typescript
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 T so that obj[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: keyof inside 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

keyoftypeof
InputA typeA value or expression
OutputString literal union of property namesThe inferred type of the value
Works onInterfaces, type aliases, classesVariables, objects, functions, expressions
Runtime costNoneNone
Common comboT[K] indexed accesskeyof typeof obj
Typical useConstrain key parametersReuse 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

typescript
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

typescript
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

typescript
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+

typescript
// 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

typescript
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

typescript
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 ConfigKey

as 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

typescript
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 EventName

keyof 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 ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?