Skip to content

Mapped Types

A mapped type iterates over the keys of a type and produces a new type. The syntax mirrors an object type, but with [K in ...] where the keys would be — a type-level for loop.

type Flags = {
[K in "a" | "b" | "c"]: boolean;
};
// ^? { a: boolean; b: boolean; c: boolean }

K ranges over each member of the union "a" | "b" | "c", and for each one the type produces a property. Combine that with keyof and you can iterate over an existing type:

interface User { id: number; name: string; }
type Stringify = {
[K in keyof User]: string;
};
// ^? { id: string; name: string }

Inside the loop, T[K] (indexed access) gives you the original value type — so you can transform it rather than replace it:

type Getters<T> = {
[K in keyof T]: () => T[K];
};
type UserGetters = Getters<User>;
// ^? { id: () => number; name: () => string }

Each property becomes a function returning that property original type. This “keep the keys, transform the values” shape is the everyday use of mapped types.

Mapping modifiers: add or remove readonly and ?

Section titled “Mapping modifiers: add or remove readonly and ?”

Mapped types can also change the modifiers on each property. You add readonly or ? by writing them, and you remove them with a - prefix.

// Make every property optional (this is how Partial works)
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Make every property readonly (this is Readonly)
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// Strip optional AND readonly off every property
type Concrete<T> = {
-readonly [K in keyof T]-?: T[K];
};
flowchart LR
  src["T: { id: number, name: string }"] -->|"for each K in keyof T"| loop["apply transform:
modifier + value type"]
  loop --> out["new type:
{ id?: number, name?: string }"]
A mapped type transforms each property

The -? removes optionality and -readonly removes immutability — which is exactly how the built-in Required and a mutable-clone utility are written. When you see -? in a library, it means “force every property to be present.”

A newer feature lets you rename or filter keys during the map using an as clause. This is how you build things like a “getters” type with get-prefixed names, or drop keys whose value type you do not want:

// Rename each key to get<Key>
type Getters2<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UG = Getters2<{ name: string }>;
// ^? { getName: () => string }
// Filter: keep only string-valued properties (remap unwanted keys to never)
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};

Remapping a key to never removes it from the result — the standard trick for filtering properties by their value type.

What is a mapped type?
Inside `{ [K in keyof T]: ... }`, how do you reference the original value type at key K?
What does the `-?` modifier do in a mapped type?
In a key-remapping `as` clause, what happens when you remap a key to `never`?