Lesson 18 · Module 3.1 — Type-level programming

Mapped types: { [K in keyof T]: … } walks the keys

You already loop over an object's keys at runtime with for (const k in obj). A mapped type is the same loop, one level up — it walks every key of a type and rebuilds the object, key by key. Pair it with indexed access T[K] (the type-level version of obj[k]) and you can read most of the standard utility types on sight.

The one rule

[K in keyof T] is a for-each over the keys of T. Inside the body, K is the current key and T[K] is the type of the value at that key. Whatever you write after the : becomes the new value type for that key.

Reading the two pieces: keyof and T[K]

Two operators do all the work. keyof T is the union of T's keys; T[K] is the value type stored under key K. They are the type-level mirrors of Object.keys and obj[k]:

type User = { id: number; name: string };

type Keys = keyof User;       // "id" | "name"   (a union of the key names)
type Val  = User["name"];     // string          (indexed access: the value's type)
type Any  = User[keyof User]; // number | string (index by the whole union)

That last line matters for the rest of the module: indexing an object type by a union of keys gives you the union of the value types. Hold that thought.

The map: rebuild an object key by key

A mapped type sits inside { … } and reads almost like English: for each K in keyof T, the new value is …. Here is a Partial-like helper that makes every property optional — the ? is the only change to the body:

type MyPartial<T> = {
  [K in keyof T]?: T[K];   // for each key K of T → same value type, now optional
};

type P = MyPartial<User>;
// resolves to:  { id?: number; name?: string }

Walk it by hand. keyof User is "id" | "name". The loop visits K = "id" (value User["id"] = number), then K = "name" (value string), copying each across with a ? bolted on. That is the entire machinery behind the built-in Partial<T>.

Transforming the value, not just copying it

The body after the : is an ordinary type expression, so you can transform each value. Wrap every property in a getter function, reading T[K] for the original type:

type Getters<T> = {
  [K in keyof T]: () => T[K];   // each value becomes a 0-arg function returning T[K]
};

type G = Getters<User>;
// resolves to:  { id: () => number; name: () => string }

The shape (the keys) is preserved; only the value types change. When you read any mapped type, do exactly this: name the key set on the left of in, then substitute one key at a time and see what the body produces. Everything else in this module is a variation on these two moves.

Don't confuse the two brackets

[K in keyof T] (inside an object type) is the mapped-type loop — it declares a key variable. T[K] is indexed access — it looks up a value type. Same square brackets, different jobs: the first walks keys, the second reads a value.

Check yourself

Recall, don’t re-read. (Answers reveal on click.)

In { [K in keyof T]: T[K] }, what does the inner T[K] denote?
What does MyPartial<{ a: number }> = { [K in keyof T]?: T[K] } resolve to?
For type U = { a: number; b: string }, what is U[keyof U]?

Primary source

The official handbook page is TypeScript Handbook — Mapped Types, with indexed access covered in Indexed Access Types. Best way to feel it: paste MyPartial and Getters into the TS Playground and hover the result to watch each key get rebuilt.

Try it yourself

Staring at a utility type and unsure what the loop produces? Paste it into the Playground and walk the keys one at a time. Next lesson (3.2) adds the as clause, which lets the loop rename or drop keys instead of merely copying them.