Mapped types create new types by transforming properties of existing types.
Basic Mapped Type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }
Common Mapped Types
Partial (Built-in)
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; }
Required (Built-in)
type Required<T> = {
[P in keyof T]-?: T[P]; // -? removes optional
};
interface User {
name?: string;
age?: number;
}
type RequiredUser = Required<User>;
// { name: string; age: number; }
Pick (Built-in)
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User {
name: string;
age: number;
email: string;
}
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string; }
Custom Mapped Types
Nullable
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
interface User {
name: string;
age: number;
}
type NullableUser = Nullable<User>;
// { name: string | null; age: number | null; }
Optional
type Optional<T> = {
[P in keyof T]?: T[P];
};
// Same as Partial, but shown as example
Stringify
type Stringify<T> = {
[P in keyof T]: string;
};
interface User {
name: string;
age: number;
}
type StringifiedUser = Stringify<User>;
// { name: string; age: string; }
Modifiers
Adding readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
Removing readonly
type Writable<T> = {
-readonly [P in keyof T]: T[P];
};
Making optional
type Optional<T> = {
[P in keyof T]?: T[P];
};
Removing optional
type Required<T> = {
[P in keyof T]-?: T[P];
};
Key Remapping (TypeScript 4.1+)
Transform keys in mapped types.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; }
Filtering Properties
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
interface User {
name: string;
greet(): void;
age: number;
}
type FunctionProps = FunctionPropertyNames<User>;
// "greet"
Exercises
Exercise 1: Basic Mapped Type
// Create a mapped type 'Optional' that:
// - Makes all properties optional
// - Apply it to an interface
Exercise 2: Readonly Mapped Type
// Create a mapped type 'Immutable' that:
// - Makes all properties readonly
// - Apply it to an interface
Exercise 3: Transform Values
// Create a mapped type 'Promisify' that:
// - Makes all properties return Promises
// - Example: { name: string } becomes { name: Promise<string> }
Exercise 4: Key Remapping
// Create a mapped type 'Setters' that:
// - Transforms keys to setter methods
// - Example: { name: string } becomes { setName: (value: string) => void }
Exercise 5: Filter Properties
// Create a mapped type that:
// - Filters out function properties
// - Keeps only data properties
Best Practices
- Use built-in utilities when possible - Partial, Required, etc.
- Create reusable mapped types - For common transformations
- Document complex mapped types - Explain the transformation
- Test mapped types - Ensure they work as expected
- Combine with other utilities - Chain transformations
Common Mistakes
- Forgetting keyof
// ❌ Wrong
type Bad<T> = {
[P in T]: T[P]; // Error: T is not a union type
};
// ✅ Correct
type Good<T> = {
[P in keyof T]: T[P];
};
- Wrong modifier syntax
// ❌ Wrong
type Bad<T> = {
[P in keyof T]?: T[P]; // This makes optional, not readonly
};
// ✅ Correct for readonly
type Good<T> = {
readonly [P in keyof T]: T[P];
};
- Not understanding distribution
// Mapped types work on object types
// They don't distribute over unions the same way conditional types do
Next Steps
Learn about Conditional Types.