Mapped Types
Mapped types let you build a new object type by transforming each property of an existing one. The syntax { [K in keyof T]: ... } iterates over a union of keys and produces a property for each — the type-level equivalent of Object.keys(obj).map(...). With modifiers you can add or remove readonly and optional (?), and with key remapping (as) you can rename or filter keys entirely. Every built-in utility type like Partial, Readonly, and Pick is a mapped type. This page covers the core syntax, modifiers, remapping, and value transformation.
Basic mapped type syntax
A mapped type walks each key K in a union (usually keyof T) and defines what that property’s type should become. The result is a brand-new object type.
type Stringify<T> = {
[K in keyof T]: string;
};
interface User {
id: number;
active: boolean;
}
type StringUser = Stringify<User>;
// { id: string; active: string }
[K in keyof T] is the iteration. For each key the value type is set to string, so every property of User becomes a string. You can reference the original value type with T[K] to transform rather than replace it.
Modifiers: readonly and optional
Mapped types can add readonly and ? to every property, or strip them off with the - prefix. Use +readonly/+? to add (the + is optional and usually omitted) and -readonly/-? to remove.
// Add modifiers — this is how Readonly and Partial are defined:
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type Partial<T> = { [K in keyof T]?: T[K] };
// Remove modifiers:
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
interface Config {
readonly host?: string;
}
type Solid = Required<Mutable<Config>>;
// { host: string } — readonly and ? both removed
The -readonly modifier strips readonly, and -? strips optionality (which also removes undefined from the value). Applying both to Config turns readonly host?: string into a plain required host: string.
| Modifier | Effect |
|---|---|
readonly [K in ...] | makes every property readonly |
-readonly [K in ...] | removes readonly |
[K in ...]? | makes every property optional |
[K in ...]-? | removes optional (and undefined) |
Key remapping with as
Since TypeScript 4.1 you can remap keys with an as clause. This lets you rename keys — often with template literal types — or filter keys out by mapping them to never.
// Rename keys: create getters like getId, getName.
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 }
The as clause computes a new key for each property. Mapping a key to never removes it entirely, which is how you filter:
// Keep only the string-valued properties.
type StringKeys<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type OnlyStrings = StringKeys<{ a: string; b: number; c: string }>;
// { a: string; c: string }
Filtering by remapping a key to
neveris the standard way to omit properties conditionally — there is no other built-in syntax for dropping keys inside a mapped type.
Transforming value types
Mapped types shine when you combine iteration with a transformation of T[K], optionally using a conditional type per property.
// Wrap every property value in a Promise.
type Promisify<T> = {
[K in keyof T]: Promise<T[K]>;
};
type AsyncUser = Promisify<{ id: number; name: string }>;
// { id: Promise<number>; name: Promise<string> }
// Make function-valued props stay, turn everything else nullable.
type Nullable<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : T[K] | null;
};
Promisify rewraps each value, and Nullable uses a conditional to treat methods differently from data. Because the transformation runs per key, each property can end up with a different result type.
Best Practices
- Iterate with
[K in keyof T]and referenceT[K]to transform rather than discard value types. - Use
?andreadonlymodifiers (with+/-) to add or strip optionality and immutability. - Remap keys with
as, and template literals, to rename properties consistently. - Filter properties by remapping unwanted keys to
never. - Prefer the built-in
Partial,Required,Readonly,Pick, andRecordbefore hand-rolling equivalents. - Combine mapped types with conditional types to vary the transform per property.