TypeScript Mapped Types
A mapped type builds a new type by walking over every property of an existing type and changing it in the same way.
- Mapped types = transform every property of a type
- Common built-in ones:
Partial,Readonly,Pick,Omit,Record
A small mapped type example
Starting from a Person type, one mapped type makes every property optional and another makes every property readonly.
// Small example
type Person = { name: string; age: number };
type PartialPerson = { [P in keyof Person]?: Person[P] };
type ReadonlyPerson = { readonly [P in keyof Person]: Person[P] };
Basic Mapped Type Syntax
The core syntax of a mapped type is { [P in K]: T }, where:
Pis the property name currently being visitedKis a union of the property names to walk over (oftenkeyof SomeType)Tis the type each property gets in the new type
Example
PartialPerson adds ? to every property of Person, so an object with only name is allowed. ReadonlyPerson adds readonly to every property, so assigning to age afterwards would be a compile error.
{ [P in K]: T } by transforming an interface by hand.
Declare a Person interface with name: string, age: number and email: string. Build a PartialPerson mapped type that makes every property optional and create a partialPerson object with only name: "John". Then build a ReadonlyPerson mapped type that marks every property readonly, create a full readonlyPerson (Alice, 30, alice@example.com), and leave a commented-out reassignment of age explaining it would be a read-only error. Nothing is printed.
Output: TypeScript file and captured output
// Define an object type
interface Person {
name: string;
age: number;
email: string;
}
// Create a mapped type that makes all properties optional
type PartialPerson = {
[P in keyof Person]?: Person[P];
};
// Usage
const partialPerson: PartialPerson = {
name: "John"
// age and email are optional
};
// Create a mapped type that makes all properties readonly
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P];
};
// Usage
const readonlyPerson: ReadonlyPerson = {
name: "Alice",
age: 30,
email: "alice@example.com"
};
// readonlyPerson.age = 31; // Error: Cannot assign to 'age' because it is a read-only property
Built-in Mapped Types
TypeScript's standard library already includes several useful mapped types, so you do not have to write the common ones yourself:
Partial<T>: make all properties optionalReadonly<T>: make all properties readonlyPick<T, K>: keep only a chosen set of keysOmit<T, K>: remove a set of keysRecord<K, V>: map a set of keys to one value type
Example
Each alias below applies one built-in mapped type to User. The comment after each alias shows the object type it produces. Required is also shown — it is the opposite of Partial.
User interface with id: number, name: string, email: string and isAdmin: boolean, then create type aliases using Partial<User>, Required<Partial<User>>, Readonly<User>, Pick<User, "email" | "id">, Omit<User, "id" | "isAdmin"> and Record<"admin" | "user" | "guest", string>, each followed by a comment spelling out the equivalent object type. The file only declares types and prints nothing.
Output: TypeScript file and captured output
interface User {
id: number;
name: string;
email: string;
isAdmin: boolean;
}
// Partial<T> - Makes all properties optional
type PartialUser = Partial<User>;
// Equivalent to: { id?: number; name?: string; email?: string; isAdmin?: boolean; }
// Required<T> - Makes all properties required
type RequiredUser = Required<Partial<User>>;
// Equivalent to: { id: number; name: string; email: string; isAdmin: boolean; }
// Readonly<T> - Makes all properties readonly
type ReadonlyUser = Readonly<User>;
// Equivalent to: { readonly id: number; readonly name: string; ... }
// Pick<T, K> - Creates a type with a subset of properties from T
type UserCredentials = Pick<User, "email" | "id">;
// Equivalent to: { email: string; id: number; }
// Omit<T, K> - Creates a type by removing specified properties from T
type PublicUser = Omit<User, "id" | "isAdmin">;
// Equivalent to: { name: string; email: string; }
// Record<K, T> - Creates a type with specified keys and value types
type UserRoles = Record<"admin" | "user" | "guest", string>;
// Equivalent to: { admin: string; user: string; guest: string; }
Creating Custom Mapped Types
You can write your own generic mapped types to transform types in exactly the way you need.
Example
StringifyProperties<T> turns every property into a string. Validator<T> turns every property into a function that receives a value of that property's type (T[P]) and returns a boolean, so productValidator must supply one check per property of Product.
Product interface (id: number, name: string, price: number, inStock: boolean).
Write StringifyProperties<T> that turns every property into string, and Validator<T> that turns every property P into a function (value: T[P]) => boolean. Apply the first to get a StringProduct type and use the second to build a productValidator object whose checks are: id greater than 0, non-empty name, price at least 0, and inStock being a boolean. Nothing is printed.
Output: TypeScript file and captured output
// Base interface
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
// Create a mapped type to convert all properties to string type
type StringifyProperties<T> = {
[P in keyof T]: string;
};
// Usage
type StringProduct = StringifyProperties<Product>;
// Equivalent to: { id: string; name: string; price: string; inStock: string; }
// Create a mapped type that adds validation functions for each property
type Validator<T> = {
[P in keyof T]: (value: T[P]) => boolean;
};
// Usage
const productValidator: Validator<Product> = {
id: (id) => id > 0,
name: (name) => name.length > 0,
price: (price) => price >= 0,
inStock: (inStock) => typeof inStock === "boolean"
};
Modifying Property Modifiers
A mapped type can also add or remove the property modifiers readonly and ? (optional). Writing +readonly or readonly adds the modifier; writing -readonly removes it. The same works for ? with -?.
Example
Mutable<T> uses -readonly to strip the read-only flag from every property of Configuration. RequiredProps<T> uses -? to make every optional property required.
readonly and ? modifiers with -readonly and -?.
Declare a Configuration interface with readonly apiKey: string, readonly apiUrl: string, timeout?: number and retries?: number. Write Mutable<T> (strips readonly from every property) and RequiredProps<T> (strips ? from every property), apply each to Configuration as MutableConfig and RequiredConfig, and comment the equivalent object type after each. The file only declares types and prints nothing.
Output: TypeScript file and captured output
// Base interface with some readonly and optional properties
interface Configuration {
readonly apiKey: string;
readonly apiUrl: string;
timeout?: number;
retries?: number;
}
// Remove readonly modifier from all properties
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// Usage
type MutableConfig = Mutable<Configuration>;
// Equivalent to: { apiKey: string; apiUrl: string; timeout?: number; retries?: number; }
// Make all optional properties required
type RequiredProps<T> = {
[P in keyof T]-?: T[P];
};
// Usage
type RequiredConfig = RequiredProps<Configuration>;
// Equivalent to: { readonly apiKey: string; readonly apiUrl: string; timeout: number; retries: number; }
Advanced Mapped Types
Mapped types become even more powerful when you combine them with conditional types. The new type of each property can then depend on the old one.
Example
FormattedResponse<T> checks each property with T[P] extends number and converts numeric properties to string. StringPropsOnly<T> uses an as clause to rename keys: properties that are not strings are mapped to never, which drops them from the result.
ApiResponse interface (data: unknown, status: number, message: string, timestamp: number).
Write FormattedResponse<T> that maps every number property to string and leaves the others unchanged, and StringPropsOnly<T> that uses an as key-remapping clause to keep only the properties whose type is string. Apply both to ApiResponse and comment the resulting types. Nothing is printed.
Output: TypeScript file and captured output
// Base interface
interface ApiResponse {
data: unknown;
status: number;
message: string;
timestamp: number;
}
// Conditional mapped type: Convert each numeric property to a formatted string
type FormattedResponse<T> = {
[P in keyof T]: T[P] extends number ? string : T[P];
};
// Usage
type FormattedApiResponse = FormattedResponse<ApiResponse>;
// Equivalent to: { data: unknown; status: string; message: string; timestamp: string; }
// Another example: Filter for only string properties
type StringPropsOnly<T> = {
[P in keyof T as T[P] extends string ? P : never]: T[P];
};
// Usage
type ApiResponseStringProps = StringPropsOnly<ApiResponse>;
// Equivalent to: { message: string; }
Key Takeaways
Mapped types let you transform every property of a type in a consistent way.
Key concepts:
- Type transformation: change property types in bulk
- Property modifiers: add or remove
readonlyand? - Key remapping: rename or filter properties with an
asclause - Composition: combine mapped types with other TypeScript features such as conditional types
Common use cases:
- Creating read-only versions of types
- Making all properties optional or required
- Transforming property types (for example to nullable or readonly)
- Filtering properties based on their types
- Creating type-safe utility functions