Mapped Types in TypeScript
A mapped type iterates over a union of keys and produces a new object type. Use it to transform every property of a type in one declaration.
A TypeScript mapped type takes an existing type and produces a new one by transforming every property. It iterates over each key of the source type and applies a rule to create the new property. The result is a type with the same keys but potentially different value types or modifiers.
This is the mechanism behind built-in utilities like Partial, Required, and Readonly. Understanding mapped types lets you build your own when the standard ones do not fit your data model.
The syntax looks like an index signature with an in keyword, where the keyof T part produces a union of all keys in T. The in keyword iterates over that union, binding each key to K, and the right side defines the new property type.
type AllBoolean<T> = {
[K in keyof T]: boolean;
};
interface Features {
darkMode: () => void;
notifications: () => void;
}
type FeatureFlags = AllBoolean<Features>;FeatureFlags resolves to { darkMode: boolean; notifications: boolean }. The original property types, both functions, are replaced with boolean. The keys are preserved exactly.
The basic mapped type syntax
A mapped type has three parts: the key iterator, the key source, and the value expression. Seeing the plainest possible mapped type makes it easier to recognize the pattern once modifiers and transformations get added on top.
type Mapped<T> = {
[K in keyof T]: T[K];
};The iterator says "for each key K in the keys of T". The value expression says "use the original type of that key". This specific mapped type is an identity, since it produces the same type as T. It is not useful on its own, but it shows the basic shape.
The real power comes from changing the value expression or adding modifiers. Here is a mapped type that makes every property optional and readonly:
type ReadonlyPartial<T> = {
readonly [K in keyof T]?: T[K];
};
interface Task {
title: string;
dueDate: Date;
}
type DraftTask = ReadonlyPartial<Task>;DraftTask resolves to { readonly title?: string; readonly dueDate?: Date }. Two modifiers are applied in one mapped type. This is more than Partial and Readonly can do individually because they only apply one modifier each.
Mapping modifiers: readonly and ?
Two modifiers can appear before the key iterator: readonly and the optional question mark. Adding them applies the modifier to every property in the result. Prefixing them with a minus sign removes the modifier if it existed in the source type.
type A<T> = { readonly [K in keyof T]: T[K] }; // adds readonly
type B<T> = { [K in keyof T]?: T[K] }; // adds optional
type C<T> = { -readonly [K in keyof T]: T[K] }; // removes readonly
type D<T> = { [K in keyof T]-?: T[K] }; // removes optionalThe removal modifiers are how Required is implemented internally. It strips the optional marker from every property, which is the opposite of what Partial does.
Here is an example that removes readonly from a type, the opposite of the built-in Readonly:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface Frozen {
readonly id: number;
readonly name: string;
}
type Thawed = Mutable<Frozen>;Thawed resolves to { id: number; name: string }. Both readonly modifiers are removed. This is the standard way to create a mutable version of a read-only type.
Transforming value types
The value expression on the right side of a mapped type does not need to reuse the original property type. It can be any type expression, including conditional types or other utility types.
type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
interface Profile {
name: string;
avatar: string;
}
type OptionalProfile = Nullable<Profile>;OptionalProfile resolves to { name: string | null; avatar: string | null }. Every property type gains a null option in its union. This is a common pattern for types that represent data from a database where any column might be null.
You can also use conditional types to transform different properties differently:
type StringifyFunctions<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? string : T[K];
};
interface API {
fetch: (url: string) => Promise<Response>;
timeout: number;
}
type DescribedAPI = StringifyFunctions<API>;DescribedAPI resolves to { fetch: string; timeout: number }. The fetch property was a function, so it became a string. The timeout property was a number, so it stayed a number. The conditional check distinguishes function properties from non-function ones, and the value expression picks a different result for each.
Mapped types vs built-in utility types
Once you understand mapped types, the built-in utilities become transparent. Here is how they are implemented:
| Utility | Mapped type equivalent |
|---|---|
| Partial<T> | { [K in keyof T]?: T[K] } |
| Required<T> | { [K in keyof T]-?: T[K] } |
| Readonly<T> | { readonly [K in keyof T]: T[K] } |
| Pick<T, K> | { [P in K]: T[P] } |
| Record<K, V> | { [P in K]: V } |
Knowing these equivalences helps you build your own when the built-in ones do not fit. For more on the built-in versions, see Partial utility type in TypeScript and Record utility type in TypeScript.
Common mistakes
Confusing mapped types with index signatures. A mapped type iterates over a known set of keys and gives you exact autocomplete. An index signature allows any key and gives you no autocomplete. Use mapped types for known key sets and index signatures for open-ended key sets.
Forgetting that mapped types are homomorphic by default. When you iterate directly over keyof T, the mapped type preserves the original modifiers from T. If a property was readonly in T, it stays readonly in the result unless you explicitly strip it with the minus-readonly syntax. This is usually what you want, but it can be surprising.
Overcomplicating value expressions. A mapped type with a deeply nested conditional in the value expression is hard to read. If the transformation is complex, break it into a separate type alias that you reference in the mapped type. For more advanced patterns, see conditional types in TypeScript.
Rune AI
Key Insights
- A mapped type uses
[K in keyof T]: NewTypeto iterate over every key of T. - Add readonly or ? as prefixes to apply modifiers, and -readonly or -? to remove them.
- The keyof operator provides the union of keys to iterate over.
- Mapped types are the foundation of built-in utilities like Partial and Required.
- Like all type-level constructs, mapped types exist only at compile time.
Frequently Asked Questions
Are mapped types the same as utility types like Partial and Required?
Can mapped types change property types instead of just modifiers?
Do mapped types work with union types, not just object types?
Conclusion
Mapped types are the foundation that built-in utility types like Partial, Required, Readonly, Pick, and Record are built on. Once you understand the [K in keyof T] syntax, you can create your own type transformations that fit your exact data model. Most real-world TypeScript codebases define at least one custom mapped type for a pattern that repeats across many interfaces.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.