Mapped Types
A mapped type builds a new object type by iterating over the keys of an existing type and transforming each property's type according to a rule — the type-level equivalent of Object.keys(obj).map(...).
🎯 What you'll learn
- What a Mapped Type Is
- Modifiers:
readonlyand? - Removing Modifiers with
- - Key Remapping with
as - Filtering Keys Out with
never
1What a Mapped Type Is
A mapped type builds a new object type by iterating over the keys of an existing type and transforming each property's type according to a rule — the type-level equivalent of Object.keys(obj).map(...).
type Flags<T> = {
[P in keyof T]: boolean;
};
interface FeatureSet { darkMode: string; betaAccess: number; }
type FeatureFlags = Flags<FeatureSet>;
// { darkMode: boolean; betaAccess: boolean } — every property's TYPE replaced, keys keptkeyof T produces a union of T's property names (its own topic); [P in <that union>] iterates each one, and whatever comes after the colon defines the new type for that key.
2Modifiers: readonly and ?
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
type MyPartial<T> = { [P in keyof T]?: T[P] };These are exactly how the real Readonly<T> and Partial<T> utility types (Utility Types topic) are implemented — mapped types with a modifier applied uniformly to every property.
3Removing Modifiers with -
type MyRequired<T> = { [P in keyof T]-?: T[P] }; // REMOVES optionality
type MyMutable<T> = { -readonly [P in keyof T]: T[P] }; // REMOVES readonly+ is also valid but is the default and rarely written explicitly (+? and ? mean the same thing) — - is the one you actually need, to strip a modifier that the source type already has.
4Key Remapping with as
type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};
interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }The as clause lets a mapped type rename each key using a template literal type, not just transform the value — this combination (mapped type + template literal key remapping) is how many advanced "generate an API surface from a data shape" patterns are built.
5Filtering Keys Out with never
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P];
};
interface Mixed { name: string; age: number; active: boolean; }
type NonBoolean = OmitByType<Mixed, boolean>; // { name: string; age: number }Mapping a key to never inside the as clause removes that key from the resulting type entirely — this is the standard technique for building conditional key-filtering utilities like this OmitByType example.
Under the HoodHomomorphic Mapped Types▾
"Homomorphic" means the mapped type preserves the source's structure automatically
A mapped type that iterates directly over keyof T (like every example above) is called homomorphic — it automatically carries over each property's existing modifiers (whether it was already readonly, already optional) from T, unless you explicitly add/remove them with +/-. This is why Partial<T> only needs to add ? — it doesn't need to separately preserve whatever readonly status each property already had; homomorphic mapping does that automatically as part of the mechanism.
interface Config {
readonly host: string;
port?: number;
}
type PartialConfig = Partial<Config>;
// { readonly host?: string; port?: number }
// ^ readonly PRESERVED automatically — Partial never mentioned readonly at allNon-homomorphic mapped types don't get this — you're on your own
type AlwaysThree<K extends string> = { [P in K]: number }; // K is not `keyof` anything specificWhen the key union being mapped over doesn't come directly from keyof T (here, K is just any string union, not tied to a specific source type's keys), there's no original property to copy modifiers from — so the mapped type is built from scratch, with no automatic readonly/optional preservation, because there's nothing there to preserve.
Key remapping's as clause is evaluated per-key, and never is a real removal signal
For section 5's filtering pattern, the checker evaluates the conditional inside as once per key during iteration; when that conditional resolves to never for a given key, the checker's mapped-type construction logic treats a never-mapped key as absent from the resulting type — not present-with-type-never, genuinely removed. This is a deliberate, specified behavior of the key-remapping feature (not a side effect of never's general "no possible values" meaning) that makes conditional key filtering possible at all.
Mapped types over keyof T are why T[P] (indexed access) inside the mapping works
Inside a homomorphic mapped type, P is a genuine type variable ranging over T's actual key union — T[P] (an indexed access type, briefly touched on in the Keyof/Typeof topic) looks up each specific property's original type as the loop iterates, which is exactly the mechanism that lets Readonly<T>'s definition { readonly [P in keyof T]: T[P] } keep every property's original type while only changing its modifiers.
⚠️ Common Pitfalls
- Trying to iterate over a key union not derived from
keyof Tand expecting modifier preservation anyway — only homomorphic mapping (iteratingkeyof Tdirectly) preserves modifiers automatically. - Forgetting
neverin anasclause removes the key rather than including it with typenever— an easy source of confusion when first learning key remapping. - Using
+readonly/+?when the intent was actually to remove a modifier —+is the (redundant) default; you almost always want-when the goal is stripping an existing modifier. - Not realizing built-in utility types ARE mapped types — see the Utility Types topic's "Under the Hood" section; if a mapped type isn't behaving as expected, comparing it against
Partial/Readonly's real definitions is often the fastest way to spot the issue.
📋 Quick Reference
{ [P in keyof T]: T[P] } → homomorphic mapped type, preserves modifiers automatically
{ [P in keyof T]?: T[P] } → adds optional
{ [P in keyof T]-readonly: T[P] } → removes readonly
{ [P in keyof T as NewKey]: T[P] } → key remapping
{ [P in keyof T as (cond ? never : P)]: T[P] } → conditional key removal🎤 Interview Q&A
Q1What is a mapped type?core▾
A type that transforms each property of another: { [K in keyof T]: Transform<T[K]> }, with modifiers readonly, ?, and removal via -readonly/-?.
- Built-in examples? Partial, Readonly, Record.
Q2What is key remapping?core▾
Using as in mapped types to rename or filter keys: `{ [K in keyof T as get${Capitalize<string & K>}]: () => T[K] } generates getters; mapping to never` removes keys.
- Filter by value type?
[K in keyof T as T[K] extends Function ? never : K].
Q3How would you make all nested properties optional?senior▾
A recursive mapped type: type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] }, handling arrays and functions specially.
- Why special-case arrays? To keep array structure correct.
Q4How do mapped types help with forms or APIs?core▾
Deriving error maps ({ [K in keyof Form]?: string }), touched-state maps, or DTO variants from a single source type, keeping them in sync.
- Benefit? One source of truth.
Q5Are mapped types homomorphic?core▾
Mapping over keyof T preserves modifiers (optional/readonly) of the original properties; mapping over arbitrary keys doesn't.
- Why does it matter? Partial<T> keeps readonly flags, for example.
🛠️ Practice exercises
Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.