TypeScript Mapped Types Cheat Sheet
Covers the [K in keyof T] syntax, key remapping with as, readonly/optional modifiers, and template literal key patterns.
Basic Mapped Type Syntax
Iterate over a union of keys and build a new object type from them.
type User = { id: number; name: string; email: string };type ReadonlyUser = { readonly [K in keyof User]: User[K] };type PartialUser = { [K in keyof User]?: User[K] };type Stringify<T> = { [K in keyof T]: string };type UserStrings = Stringify<User>;// { id: string; name: string; email: string }
Adding & Removing Modifiers
Prefix with `+`/`-` to explicitly add or strip `readonly`/`?`.
type Mutable<T> = { -readonly [K in keyof T]: T[K] };type Required2<T> = { [K in keyof T]-?: T[K] };type Optional<T> = { [K in keyof T]+?: T[K] };interface Config { readonly host: string; port?: number;}type WritableConfig = Mutable<Config>; // host is no longer readonlytype FullConfig = Required2<Config>; // port is no longer optional
Key Remapping with `as`
TS 4.1+ lets you transform key names during mapping, or filter keys out entirely.
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]};interface Person { name: string; age: number }type PersonGetters = Getters<Person>;// { getName: () => string; getAge: () => number }// Filter out keys by mapping them to `never`:type OmitByType<T, V> = { [K in keyof T as T[K] extends V ? never : K]: T[K]};type NonStringProps = OmitByType<Person, string>; // { age: number }
Mapped Type Syntax Reference
Building blocks for constructing mapped types.
- [K in keyof T]- iterates every key of T, K bound to each key
- as- remaps the resulting key name (TS 4.1+); map to never to drop a key
- readonly / -readonly- add or strip the readonly modifier
- ? / -?- add or strip optionality
- T[K]- indexed access to get the value type for key K
- keyof T- union of all property names of T, the usual source for K
Mapped Types Over Tuples & Arrays
Homomorphic mapping over `keyof T` preserves tuple length, labels, and array-ness instead of collapsing to a plain object.
type Labeled<T extends readonly unknown[]> = { [K in keyof T]: { value: T[K] }};type L = Labeled<[string, number, boolean]>;// [{ value: string }, { value: number }, { value: boolean }]type PartialTuple<T extends readonly unknown[]> = { [K in keyof T]?: T[K] };type PT = PartialTuple<[string, number]>; // [string?, number?]// `keyof` on a tuple includes numeric indices plus inherited array// members (length, push, etc.) — mapped types filter to the numeric keys.
Recursive Mapped Types (DeepReadonly)
A mapped type can call itself in the value position to transform nested object graphs.
type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]};interface State { user: { name: string; roles: string[] }; count: number;}type FrozenState = DeepReadonly<State>;// { readonly user: { readonly name: string; readonly roles: readonly string[] }; readonly count: number }
Filtering + Renaming Keys with `as` and Template Literals
Combine a template literal pattern-match with the `as` clause to derive a filtered, renamed key set in one pass.
interface Handlers { onClick: (e: MouseEvent) => void; onHover: (e: MouseEvent) => void; label: string; // not a handler — should be dropped}type EventNameOf<K> = K extends `on${infer Rest}` ? Uncapitalize<Rest> : never;type EventMap<T> = { [K in keyof T as EventNameOf<K & string>]: T[K]};type Evts = EventMap<Handlers>;// { click: (e: MouseEvent) => void; hover: (e: MouseEvent) => void }
Homomorphic vs. Non-Homomorphic Mapped Types
Mapping over `keyof T` copies T's structure and modifiers; mapping over a standalone key union does not.
// Homomorphic: iterates `keyof T`, so it copies T's modifiers and shape.type Partial2<T> = { [K in keyof T]?: T[K] };// Non-homomorphic: iterates an independent key union, so there is no// source structure to copy modifiers/array-ness from.type PartialRecord<K extends string, V> = { [P in K]?: V };type Arr = string[];type A1 = Partial2<Arr>; // still an array-shaped type: (string | undefined)[]type A2 = PartialRecord<'a' | 'b', string>; // plain object: { a?: string; b?: string }
Advanced Mapped Type Patterns
Idioms that show up repeatedly in utility-type libraries.
- Homomorphic mapping- `{ [K in keyof T]: ... }` preserves T's readonly/optional modifiers and array/tuple shape automatically
- Non-homomorphic mapping- `{ [K in SomeUnion]: ... }` builds a plain object with no source structure to inherit from
- Drop a key via `as never`- map a key to `never` inside the `as` clause to remove it from the result entirely
- Template literal remapping- derive new key names like `on${K}` or `get${Capitalize<K>}` inside the `as` clause
- Recursive mapped type- self-reference in the value position to deep-transform nested objects (DeepReadonly, DeepPartial)
- Distributive homomorphic mapping- applying a homomorphic mapped type to a union of object types maps each union member independently before the union re-forms
- Mapped type + conditional combo- pair `[K in keyof T]` with a conditional in the value position to build type-aware Pick/Omit variants
Combine key remapping with template literal types (e.g. `on${Capitalize<K>}`) to auto-generate event-handler-shaped types from a props interface — this is exactly how libraries type their `onX` callback props.