Reading — step 1 of 7
Learn
Mapped types transform every property of an existing type. They power TypeScript's built-in utility types and let you build custom ones for your domain.
Basic syntax
type ReadonlyAll<T> = {
readonly [K in keyof T]: T[K];
};
interface User { name: string; age: number }
type LockedUser = ReadonlyAll<User>;
// { readonly name: string; readonly age: number }
[K in keyof T] iterates over each property name. T[K] is the indexed-access type (the value type at key K).
The built-in utility types
TypeScript ships these — used constantly in real code:
interface User {
name: string;
age: number;
email: string;
}
// Make all fields optional:
type UserUpdate = Partial<User>;
// { name?: string; age?: number; email?: string }
// Make all fields required (removes ?):
type FullUser = Required<UserUpdate>;
// Make all fields readonly:
type FrozenUser = Readonly<User>;
// Pick specific keys:
type UserContact = Pick<User, 'name' | 'email'>;
// { name: string; email: string }
// Omit specific keys:
type PublicUser = Omit<User, 'email'>;
// { name: string; age: number }
// Build a record (uniform values keyed by some union):
type Permissions = Record<'read' | 'write' | 'admin', boolean>;
// { read: boolean; write: boolean; admin: boolean }
Partial, Required, Readonly, Pick, Omit, Record — memorize these. They appear in nearly every TypeScript codebase.
Modifier control: + and -
Mapped types can ADD or REMOVE the readonly and ? modifiers explicitly:
type Mutable<T> = {
-readonly [K in keyof T]: T[K]; // remove readonly
};
type Required2<T> = {
[K in keyof T]-?: T[K]; // remove optional (?) modifier
};
That's how Required<T> strips the ? from every property.
Key remapping (TypeScript 4.1+)
Transform the keys themselves:
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// getEmail: () => string;
// }
Filter properties by mapping unwanted keys to never:
type StringProps<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type OnlyStrings = StringProps<User>;
// { name: string; email: string }
This is the canonical pattern for filtering properties by their value types.
Building your own utilities
Real-world example — a type that requires AT LEAST one of N options:
type RequireAtLeastOne<T> = {
[K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[keyof T];
type Login = RequireAtLeastOne<{ email?: string; phone?: string; username?: string }>;
// Object must have AT LEAST one of: email, phone, username
Reads like a knot, but composes the primitives — Pick, Omit, Partial, Required, mapped types — to express a precise constraint.
Common patterns
// Pull all readonly properties (excluding methods):
type ReadonlyKeys<T> = {
[K in keyof T]-?: <U>() => U extends { [P in K]: T[K] } ? 1 : 2 extends
<U>() => U extends { -readonly [P in K]: T[K] } ? 1 : 2
? K
: never;
}[keyof T];
// DeepPartial — recursive Partial:
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
The [keyof T] at the end of those patterns extracts the union of values from the mapped type — a common trick for collecting matching keys.
Common mistakes
- Using
Partialdeeply when you meanDeepPartial—Partial<{ a: { b: string } }>is{ a?: { b: string } }, not{ a?: { b?: string } }. - Confusing
PickandExtract— Pick takes object + keys; Extract takes union + filter type. - Forgetting key remapping needs
as—[K in keyof T as NewKey]is the syntax. - Hidden
string &casts on key remapping —Kiskeyof T, oftenstring | number | symbol. Capitalize and other string utilities requirestring & K. - Recursive mapped types hitting recursion limit — TypeScript caps depth (~50 since 4.7). Iterative versions via tuple
lengthworkaround can extend that.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…