Mapped Types
A loop over keys
Section titled “A loop over keys”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 }Referencing the original value type
Section titled “Referencing the original value type”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 propertytype 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 }"] 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.”
Key remapping with as
Section titled “Key remapping with as”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.