skip to content

keyof, in & Index Access

The three pieces every mapped type is built from: keyof to get the key union, indexed access to read a property's type, and `[K in ...]` to iterate. Interviewers check that you know homomorphic mapping quietly preserves modifiers, arrays, and tuples.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

4

In TypeScript, what does the `keyof` operator produce for an object type such as `interface User { id: number; name: string }`, and how do you apply it to a value like `const config = { host: 'localhost', port: 5432 }`?

level: juniorimportance: must knowfreq 75%

answer

  1. operates on the type, not the value
  2. a union, not an array
  3. keys arrive as literal types
  4. typeof first when you have a value
  5. index signature widens to string | number

basics

~20 s

keyof produces the union of a type's property keys as literal types: keyof User is "id" | "name". It operates on types only, so for a value you write keyof typeof config. Nothing of it survives compilation.

solid answer

~40 s

`keyof T` is a type-level operator that produces the union of `T`'s property keys as **literal types** — for `interface User { id: number; name: string }`, `keyof User` is `"id" | "name"`, not `string` and not an array. It takes a *type*, so to point it at a value you go through a type query first: `keyof typeof config` gives `"host" | "port"`. Optional properties still contribute their key, numeric and symbol keys come back as numeric and symbol literal types, and a string index signature widens the result to `string | number`. Like everything in the type layer it is erased — it emits no code, and it describes the keys the *type* declares, not whatever keys the object happens to carry at runtime.

go deeper

for a junior

Be able to say instantly that keyof User is the union of the key strings and that you need keyof typeof config when you start from a value rather than a type.

for a middle

Explain the mechanics: keys come back as literal types, numeric keys as number literals, and a string index signature widens the union to string | number. Be ready to justify each.

for a senior

Show why the runtime key list cannot be typed as (keyof T)[] — structural typing permits extra properties — and how you decide when an assertion to the narrower type is actually safe in your code.

for a principal

Own the modelling call: enumerated keys give exhaustive derived types and refactor safety, while an index signature buys openness at the cost of a useless key union. Say which one a shared type should expose and why.

## What `keyof` produces `keyof` is an operator that lives entirely in type position. Given an object type, it yields the **union of that type's property keys, each as a literal type**: ```ts interface User { id: number; name: string; isActive?: boolean; } type UserKey = keyof User; // "id" | "name" | "isActive" ``` Three things to notice. First, the result is a *union*, not an array and not a tuple — `"id" | "name" | "isActive"` is a set of alternatives, so a variable of that type holds exactly one of those strings. Second, each member is a **string literal type**, which is far more precise than `string`: assigning `"idd"` to a `UserKey` is a compile error. Third, an optional property still contributes its key — optionality affects the property's *value* type, not whether the key exists in the type. Methods count as properties, so `keyof` on a type with methods includes the method names alongside the data keys. ## It consumes a type, not a value A very common first mistake is writing `keyof config` where `config` is a runtime constant. That fails, because `config` is a value and `keyof` expects a type. The bridge is the **type query** operator `typeof`, used in type position: ```ts const config = { host: "localhost", port: 5432 }; type ConfigKey = keyof typeof config; // "host" | "port" ``` `typeof config` asks the checker for the inferred type of that binding (`{ host: string; port: number }`), and `keyof` then takes its keys. This type-position `typeof` is a different operator from the runtime `typeof` expression that returns `"object"` or `"string"`; they merely share a keyword. `keyof typeof x` is the idiom you will write constantly, especially over configuration objects and lookup tables. ## Key kinds: strings, numbers, symbols JavaScript object keys are strings or symbols, and numeric-looking keys are modelled as numeric literal types: ```ts interface StatusText { 200: string; 404: string; } type Status = keyof StatusText; // 200 | 404 ``` That is `200 | 404` — number literals, not `"200" | "404"`. A symbol-keyed property contributes a `unique symbol` type in the same way. ## Index signatures widen the result When the type does not enumerate keys but declares an index signature, `keyof` reflects the whole key space rather than a finite union: ```ts type Dict = { [key: string]: number }; type DictKey = keyof Dict; // string | number ``` `number` appears alongside `string` because a numeric key and its string form address the same property, so the checker treats numbers as usable string keys. A numeric index signature alone gives just `number`. And `keyof any` is the full key space: `string | number | symbol` — which is why generic key parameters are often constrained against it. ## It is erased `keyof` generates no output. It cannot inspect an object, it cannot enumerate anything at runtime, and it will not tell you which properties actually exist on a particular object at a particular moment. It reports what the *declared type* says. This is the same erasure rule that governs the rest of the type layer, and it explains a discrepancy people trip over: reading the keys of an object at runtime gives you `string[]`, not `(keyof User)[]`. The checker refuses the narrower typing because a value typed `User` is allowed to be an object with *extra* properties — structural typing means `User` is a lower bound on the shape, not an exact description — so promising that every runtime key is one of `"id" | "name"` would be unsound. ## Where it earns its keep On its own `keyof` looks like trivia. Its value is compositional: it is the key set that indexed access types read from (`User[keyof User]` gives the union of all *value* types), and it is the key set that a mapped type iterates over when it walks a type property by property. Nearly every derived-type pattern you will meet starts with `keyof` producing the key union, so getting the mental model right — a union of literal key types, computed from a type, erased at emit — pays off far beyond the operator itself.

  • Why is reading an object's keys at runtime typed as `string[]` rather than `(keyof T)[]`?
    Because TypeScript is structural: a value typed `User` may be an object with additional properties and still be assignable, so the actual runtime keys are a superset of the declared ones. Typing the result as `(keyof User)[]` would be a promise the type system cannot keep, so the standard signature returns `string[]` and you opt into the narrower type deliberately with an assertion if you know the object is exact.
  • What is `keyof any`, and why does it come up?
    `keyof any` is `string | number | symbol` — the complete space of JavaScript property keys. It shows up as the natural upper bound whenever something has to accept "any usable key type", for example a type parameter that will be used as a key set. It is the widest possible result `keyof` can produce.
  • Does `keyof` include optional properties?
    Yes. `keyof { a: string; b?: number }` is `"a" | "b"`. The `?` modifier changes the property's value type (adding `undefined` under strictNullChecks) and whether it must be supplied, but the key is still part of the type's declared shape, so it appears in the key union.

saying these in an interview costs you the question

  • Says keyof returns an array of key names
  • Writes keyof someValue instead of keyof typeof someValue
  • Thinks keyof gives string instead of literal key types
  • Believes keyof inspects the object at runtime
  • Assumes optional properties are left out of keyof

context

open as a page

In TypeScript, given `interface User { id: number; name?: string }` and `const roles = ['admin', 'user'] as const`, what types do the indexed access types `User['id']`, `User[keyof User]` and `(typeof roles)[number]` produce?

level: middleimportance: must knowfreq 58%

basics

~20 s

Indexed access reads a property's type by key: User['id'] is number, User[keyof User] is the union of all property types (number | string | undefined), and (typeof roles)[number] is the union of the tuple's elements, 'admin' | 'user'.

open as a page

In TypeScript, given `type A = { id: number; name: string }` and `type B = { id: number; age: number }`, what is `keyof (A | B)`, and what rule produces that answer?

level: middleimportance: should knowfreq 42%

basics

~20 s

keyof (A | B) is "id" — only the keys every member has. keyof distributes over the union and the results are intersected, because a value of a union type is guaranteed to carry just the shared properties. The mirror case: keyof (A & B) is "id" | "name" | "age".

open as a page

In TypeScript, what does a mapped type written as `{ [K in keyof T]: ... }` over a type parameter `T` preserve that a mapped type written as `{ [K in 'a' | 'b']: ... }` cannot, and why does that matter when `T` is an array or a tuple?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Mapping over keyof T is homomorphic: it copies each property's readonly and optional modifiers from T, and it maps arrays to arrays and tuples to tuples. Mapping over a fixed literal key union builds a brand-new object type with everything required, mutable and object-shaped.

open as a page