Mapped types transform existing types by iterating over their keys with { [K in keyof T]: NewType } — modifiers add/remove readonly and optional, key remapping (as clause) filters or renames keys, and they power built-in utilities like Partial, Required, Readonly, and Record.
{ [K in keyof T]: T[K] } iterates over every property of T — the foundation for transforming existing types into new ones.
Add readonly or ? with + (default), remove with -. Required<T> uses -? to make all properties required; Mutable uses -readonly.
Filter keys by mapping to never, rename with template literals, or compute new keys — enables patterns like Getters<T> and property filtering.
Partial, Required, Readonly, Pick, Omit, and Record are all mapped types — understanding the pattern helps extend and debug them.
Mapped types create new types by transforming every property of an existing type. They're TypeScript's equivalent of Array.map() but for type-level properties.
A mapped type iterates over keys and produces a new type for each:
type Readonly<T> = { readonly [K in keyof T]: T[K] };keyof T produces a union of T's property namesK in iterates over each key in the unionT[K] is an indexed access type — the type of property K on Treadonly addedMapped types can add or remove readonly and ? (optional) modifiers:
// Add readonly to all properties
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// Make all properties optional
type Partial<T> = { [K in keyof T]?: T[K] };
// Remove optional (make all required)
type Required<T> = { [K in keyof T]-?: T[K] };
// Remove readonly
type Mutable<T> = { -readonly [K in keyof T]: T[K] };The - prefix removes a modifier, + adds it (the default). Required<T> uses -? to strip optional markers from every property.
asTypeScript 4.1 added key remapping to filter, rename, or compute new keys:
// Filter: keep only string-valued properties
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K]
};
// Rename: prefix all keys with 'get'
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};Mapping a key to never removes it from the result — this is how you implement filtering. Template literal types in the as clause enable dynamic key renaming.
TypeScript's standard library includes several mapped types:
Partial<T> — all properties optionalRequired<T> — all properties requiredReadonly<T> — all properties readonlyRecord<K, V> — object with keys K and values V: { [P in K]: V }Pick<T, K> — select subset of properties: { [P in K]: T[P] }Omit<T, K> — remove properties (implemented as Pick<T, Exclude<keyof T, K>>)Understanding that these are mapped types helps debug and extend them.
You can map over any union, not just keyof T:
type EventHandlers = {
[K in 'click' | 'focus' | 'blur']: (event: Event) => void
};Record<K, V> is the built-in version of this pattern.
Combine mapped types with conditional types for deep transformations:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
};This recursively makes every nested property readonly — useful for immutable state patterns.
By default, mapped types preserve the optional and readonly modifiers of the original type. If the original property is optional, the mapped property is optional too (unless you explicitly change it with -? or -readonly).
Mapped types iterate over keys with [K in keyof T] to create new types — they're the mechanism behind Partial, Required, Readonly, Pick, and Record. Modifiers (+/- readonly and ?) control property attributes. Key remapping (as clause) enables filtering (map to never) and renaming (template literal types). Recursive mapped types with conditional types enable deep transformations.
Fun Fact
Key remapping (the as clause in mapped types) was one of the most requested TypeScript features for years before it landed in TypeScript 4.1. Before key remapping, filtering properties required a two-step pattern combining Pick and conditional types — now it's a single expression.