What is a "mapped type"?
What a Mapped Type Is
A mapped type is a type created from another type (or a set of keys) by iterating over its keys and modifying properties (their types, names, modifiers).
You could say it is "the type-level equivalent of a for...in loop over an object's keys".
Syntax
type NewType = {
[K in Keys]: Type;
}Kis a variable representing a key;inis an operator that iterates over the values of the unionKeys;Typeis the value type we assign to every key.
Example 1. A simple mapping
type User = {
name: string;
age: number;
};
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// Transformed type:
type NullableUser = Nullable<User>;
/*
{
name: string | null;
age: number | null;
}
*/Here:
keyof Tis"name" | "age";[K in keyof T]iterates over"name"and"age";- every field is assigned
T[K] | null.
Example 2. Using modifiers
In mapped types you can change the modifiers of properties:
| Modifier | What it does |
|---|---|
? | makes the property optional |
-? | makes the property required |
readonly | makes the property read-only |
-readonly | removes readonly |
Example
type Optional<T> = {
[K in keyof T]?: T[K];
};
type ReadonlyCopy<T> = {
readonly [K in keyof T]: T[K];
};
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type RequiredCopy<T> = {
[K in keyof T]-?: T[K];
};Example 3. Implementing the standard utilities
This is how many of the built-in utility types are constructed:
// Partial<T>
type Partial<T> = {
[K in keyof T]?: T[K];
};
// Required<T>
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Readonly<T>
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// Pick<T, K>
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Record<K, T>
type Record<K extends string | number | symbol, T> = {
[P in K]: T;
};Example 4. Transforming keys
You can rename properties using the as construct (TypeScript >= 4.1):
type UppercaseKeys<T> = {
[K in keyof T as Uppercase<string & K>]: T[K];
};
type User = {
name: string;
age: number;
};
type UpperUser = UppercaseKeys<User>;
/*
{
NAME: string;
AGE: number;
}
*/Here
as Uppercase<...>renames the keys to uppercase.
Example 5. Filtering keys
You can even remove keys from a type using never:
type RemoveNever<T> = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
};
type Example = {
a: string;
b: never;
c: number;
};
type Clean = RemoveNever<Example>;
// { a: string; c: number }Example 6. Combining with conditional types
Mapped types are often combined with conditional types for flexible transformations:
type MakeOptionalIfNullable<T> = {
[K in keyof T as null extends T[K] ? K : never]?: T[K];
};
type User = {
id: number;
name: string | null;
age: number;
};
type OptionalNullable = MakeOptionalIfNullable<User>;
// { name?: string | null }Example 7. Dynamically building API types
type Events = "click" | "submit" | "hover";
type EventHandlers = {
[E in Events as `on${Capitalize<E>}`]: (event: E) => void;
};
/*
{
onClick: (event: "click") => void;
onSubmit: (event: "submit") => void;
onHover: (event: "hover") => void;
}
*/How TypeScript thinks about it
TypeScript essentially does the following:
// pseudocode
let result = {};
for (let key of Keys) {
result[key] = Type;
}
return result;Advantages of mapped types
They let you automatically derive types
They eliminate code duplication (you do not have to rewrite fields by hand)
They work great with keyof, Conditional Types, Template Literals
They are the basis of all built-in utility types
Summary
| Concept | Description |
|---|---|
| Mapped type | A type created by iterating over the keys of another type |
| Syntax | [K in Keys]: Type |
| Extra capabilities | readonly, ?, as, never |
| Based on | keyof, in, conditional types |
| Examples in TS | Partial, Required, Readonly, Pick, Record |
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.