Mapped types transform existing types into new ones. Here's how to use them effectively.
Basic Mapped Types#
1// Iterate over keys
2type Stringify<T> = {
3 [K in keyof T]: string;
4};
5
6interface User {
7 id: number;
8 name: string;
9 active: boolean;
10}
11
12type StringUser = Stringify<User>;
13// { id: string; name: string; active: string; }
14
15// Preserve value types
16type Clone<T> = {
17 [K in keyof T]: T[K];
18};
19
20// Make all properties optional
21type MyPartial<T> = {
22 [K in keyof T]?: T[K];
23};
24
25// Make all properties required
26type MyRequired<T> = {
27 [K in keyof T]-?: T[K];
28};
29
30// Make all properties readonly
31type MyReadonly<T> = {
32 readonly [K in keyof T]: T[K];
33};
34
35// Remove readonly
36type Mutable<T> = {
37 -readonly [K in keyof T]: T[K];
38};Key Filtering#
1// Filter keys by value type
2type FilterByType<T, U> = {
3 [K in keyof T as T[K] extends U ? K : never]: T[K];
4};
5
6interface Mixed {
7 id: number;
8 name: string;
9 count: number;
10 active: boolean;
11}
12
13type NumberProps = FilterByType<Mixed, number>;
14// { id: number; count: number; }
15
16type StringProps = FilterByType<Mixed, string>;
17// { name: string; }
18
19// Exclude keys
20type ExcludeKeys<T, K extends keyof T> = {
21 [P in keyof T as P extends K ? never : P]: T[P];
22};
23
24type WithoutId = ExcludeKeys<User, 'id'>;
25// { name: string; active: boolean; }
26
27// Pick keys
28type PickKeys<T, K extends keyof T> = {
29 [P in K]: T[P];
30};
31
32type OnlyId = PickKeys<User, 'id'>;
33// { id: number; }Key Remapping#
1// Rename keys
2type Getters<T> = {
3 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
4};
5
6interface Person {
7 name: string;
8 age: number;
9}
10
11type PersonGetters = Getters<Person>;
12// { getName: () => string; getAge: () => number; }
13
14// Setters
15type Setters<T> = {
16 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
17};
18
19type PersonSetters = Setters<Person>;
20// { setName: (value: string) => void; setAge: (value: number) => void; }
21
22// Prefix keys
23type Prefixed<T, P extends string> = {
24 [K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
25};
26
27type UserPrefixed = Prefixed<User, 'user'>;
28// { userId: number; userName: string; userActive: boolean; }
29
30// Event handlers
31type EventHandlers<T> = {
32 [K in keyof T as `on${Capitalize<string & K>}Change`]: (value: T[K]) => void;
33};
34
35type PersonHandlers = EventHandlers<Person>;
36// { onNameChange: (value: string) => void; onAgeChange: (value: number) => void; }Conditional Mapping#
1// Different transformation based on type
2type Serialize<T> = {
3 [K in keyof T]: T[K] extends Date
4 ? string
5 : T[K] extends object
6 ? Serialize<T[K]>
7 : T[K];
8};
9
10interface Event {
11 id: number;
12 name: string;
13 date: Date;
14 metadata: {
15 created: Date;
16 updated: Date;
17 };
18}
19
20type SerializedEvent = Serialize<Event>;
21// {
22// id: number;
23// name: string;
24// date: string;
25// metadata: { created: string; updated: string; }
26// }
27
28// Nullable properties
29type Nullable<T> = {
30 [K in keyof T]: T[K] | null;
31};
32
33// Deep partial
34type DeepPartial<T> = {
35 [K in keyof T]?: T[K] extends object
36 ? DeepPartial<T[K]>
37 : T[K];
38};
39
40// Deep readonly
41type DeepReadonly<T> = {
42 readonly [K in keyof T]: T[K] extends object
43 ? DeepReadonly<T[K]>
44 : T[K];
45};Union to Object#
1// Create object type from union
2type UnionToObject<U extends string, V = boolean> = {
3 [K in U]: V;
4};
5
6type Permissions = 'read' | 'write' | 'delete';
7
8type PermissionFlags = UnionToObject<Permissions>;
9// { read: boolean; write: boolean; delete: boolean; }
10
11type PermissionValues = UnionToObject<Permissions, number>;
12// { read: number; write: number; delete: number; }
13
14// Record-like
15type MyRecord<K extends string | number | symbol, V> = {
16 [P in K]: V;
17};
18
19type StringRecord = MyRecord<string, number>;
20// { [key: string]: number; }Property Modifiers#
1// Add modifiers
2type AddOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
3
4interface User {
5 id: number;
6 name: string;
7 email: string;
8}
9
10type UserWithOptionalEmail = AddOptional<User, 'email'>;
11// { id: number; name: string; email?: string; }
12
13// Remove modifiers from specific keys
14type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
15
16interface Config {
17 host?: string;
18 port?: number;
19 debug?: boolean;
20}
21
22type ConfigWithHost = RequireKeys<Config, 'host'>;
23// { host: string; port?: number; debug?: boolean; }
24
25// Make specific keys readonly
26type ReadonlyKeys<T, K extends keyof T> = Omit<T, K> & Readonly<Pick<T, K>>;
27
28type UserWithReadonlyId = ReadonlyKeys<User, 'id'>;
29// { readonly id: number; name: string; email: string; }Index Signatures#
1// Mapped type with index signature
2type Dictionary<T> = {
3 [key: string]: T;
4};
5
6type NumberDict = Dictionary<number>;
7// { [key: string]: number; }
8
9// Combine with known keys
10type ConfigMap<T> = {
11 [K in keyof T]: T[K];
12} & {
13 [key: string]: unknown;
14};
15
16// Indexed access in mapped types
17type ValueOf<T> = T[keyof T];
18
19type UserValue = ValueOf<User>;
20// number | string | boolean
21
22// Get property types by key pattern
23type GettersOnly<T> = {
24 [K in keyof T as K extends `get${string}` ? K : never]: T[K];
25};Practical Examples#
1// API response wrapper
2type ApiResponse<T> = {
3 [K in keyof T]: {
4 data: T[K];
5 loading: boolean;
6 error: Error | null;
7 };
8};
9
10interface Endpoints {
11 users: User[];
12 posts: Post[];
13}
14
15type ApiState = ApiResponse<Endpoints>;
16// {
17// users: { data: User[]; loading: boolean; error: Error | null; };
18// posts: { data: Post[]; loading: boolean; error: Error | null; };
19// }
20
21// Form state
22type FormState<T> = {
23 values: T;
24 errors: { [K in keyof T]?: string };
25 touched: { [K in keyof T]?: boolean };
26};
27
28// State actions
29type Actions<T> = {
30 [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
31} & {
32 [K in keyof T as `reset${Capitalize<string & K>}`]: () => void;
33};
34
35type UserActions = Actions<User>;
36// {
37// setId: (value: number) => void;
38// setName: (value: string) => void;
39// setActive: (value: boolean) => void;
40// resetId: () => void;
41// resetName: () => void;
42// resetActive: () => void;
43// }Recursive Mapped Types#
1// Deep transform
2type DeepNullable<T> = T extends object
3 ? { [K in keyof T]: DeepNullable<T[K]> | null }
4 : T | null;
5
6// Deep required
7type DeepRequired<T> = T extends object
8 ? { [K in keyof T]-?: DeepRequired<T[K]> }
9 : T;
10
11// Flatten nested objects
12type Flatten<T, P extends string = ''> = {
13 [K in keyof T as T[K] extends object
14 ? keyof Flatten<T[K], `${P}${string & K}.`>
15 : `${P}${string & K}`
16 ]: T[K] extends object ? Flatten<T[K]>[keyof Flatten<T[K]>] : T[K];
17};
18
19// Path keys
20type Paths<T, P extends string = ''> = T extends object
21 ? {
22 [K in keyof T]: K extends string
23 ? P extends ''
24 ? K | Paths<T[K], K>
25 : `${P}.${K}` | Paths<T[K], `${P}.${K}`>
26 : never;
27 }[keyof T]
28 : never;
29
30interface Nested {
31 a: {
32 b: {
33 c: number;
34 };
35 };
36 d: string;
37}
38
39type NestedPaths = Paths<Nested>;
40// 'a' | 'a.b' | 'a.b.c' | 'd'Combining Mapped Types#
1// Intersection of mapped types
2type Combine<T> = Getters<T> & Setters<T>;
3
4type PersonMethods = Combine<Person>;
5// {
6// getName: () => string;
7// getAge: () => number;
8// setName: (value: string) => void;
9// setAge: (value: number) => void;
10// }
11
12// Union of mapped types
13type Either<T, U> = { [K in keyof T]: T[K] } | { [K in keyof U]: U[K] };
14
15// Merge with override
16type Merge<T, U> = {
17 [K in keyof T | keyof U]: K extends keyof U
18 ? U[K]
19 : K extends keyof T
20 ? T[K]
21 : never;
22};Best Practices#
Design:
✓ Use meaningful type names
✓ Document complex transformations
✓ Keep transformations simple
✓ Compose small mapped types
Performance:
✓ Avoid deep recursion
✓ Use constraints to limit keys
✓ Test with real types
✓ Check compilation time
Patterns:
✓ Key remapping for naming conventions
✓ Conditional mapping for type variations
✓ Modifiers for optional/readonly
✓ Filtering for type subsets
Conclusion#
Mapped types transform types by iterating over keys. Use them for creating variations like readonly or optional versions, generating getter/setter types, and transforming nested structures. Key remapping enables powerful naming transformations.