skip to content

Mapped Types

How you write your own utility types: iterate the keys of a type and produce a new property for each one. Interviewers reach here when they want to see whether you can build Partial or Omit yourself rather than only import them.

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

explore

questions

12

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, how do you write a `Mutable<T>` mapped type that strips `readonly` from every property of `T`, and what do the `+` and `-` prefixes mean in a mapped type?

level: middleimportance: must knowfreq 52%

basics

~20 s

Write type Mutable<T> = { -readonly [K in keyof T]: T[K] }. In a mapped type the minus prefix strips a modifier the source property carried and plus adds one; both prefixes apply to readonly and to the optional marker ?.

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 a TypeScript mapped type, what does the `as` clause in `{ [K in keyof T as NewKey]: T[K] }` do, and what happens to a key whose `as` clause evaluates to `never`?

level: middleimportance: should knowfreq 45%

basics

~20 s

The as clause renames each key while the mapped type iterates, emitting the property under the computed name instead of K. A key whose clause evaluates to never produces no property at all, which is how mapped types filter keys out.

open as a page

In TypeScript, how do you write a mapped type that keeps only the properties of `T` whose values are functions, and where does the conditional have to go?

level: middleimportance: should knowfreq 32%

basics

~20 s

Put the conditional in the as clause: { [K in keyof T as T[K] extends Fn ? K : never]: T[K] }. Keys whose value type fails the test map to never and disappear. A conditional in the value position would keep the key instead.

open as a page

With `strictNullChecks` on, the TypeScript mapped type `type Concrete<T> = { [K in keyof T]-?: T[K] }` is applied to `{ a?: string }`. What is the type of `a` on the result, and what exactly did `-?` do?

level: middleimportance: should knowfreq 40%

basics

~20 s

The result is a required a of type string, not string | undefined. Under strictNullChecks the minus-question-mark modifier does two things: it drops the optional marker and it removes undefined from the property type it copied over.

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

You describe an API response in TypeScript by remapping its snake_case keys to camelCase with a mapped type's `as` clause. What does that type actually guarantee about the object you receive at runtime, and how do you close the gap?

level: seniorimportance: should knowfreq 26%

basics

~20 s

Nothing. A key-remapped type is erased at compile time, so the received object still has its original snake_case keys. You close the gap with a real mapping function whose return type is the remapped type, plus validation of the payload at the boundary.

open as a page

A TypeScript service declares its startup config with `readonly` properties, then uses a helper `type Mutable<T> = { -readonly [K in keyof T]: T[K] }` to build and patch that object during boot. What protection does the `readonly` annotation actually give, and how would you structure the code instead?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Very little on its own: readonly is a compile-time annotation that is erased, is ignored when TypeScript compares object types for assignability, and is stripped by any Mutable helper. Confine mutation to a construction function that returns the readonly view once.

open as a page

In a TypeScript mapped type such as `{ +readonly [K in keyof T]+?: T[K] }`, what does the `+` prefix mean, and how does that type differ from `{ readonly [K in keyof T]?: T[K] }`?

level: juniorimportance: nice to knowfreq 24%

basics

~20 s

The plus prefix means add the modifier, which is already what a bare readonly or ? means, so the two mapped types describe exactly the same type. The plus exists only to make the direction explicit alongside the minus form.

open as a page

In a TypeScript codebase you can either derive your internal types from external payload types with key-remapping mapped types, or declare them by hand. How do you decide, and what does the derived approach cost a team?

level: principalimportance: nice to knowfreq 16%

basics

~20 s

Derive when the two shapes must stay in lockstep and the external type is genuinely the source of truth. Declare by hand when the internal model should evolve independently. Derivation kills drift but produces key names that no code search can find.

open as a page