Suggest an editImprove this articleRefine the answer for “What are mapped types in TypeScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Mapped types** create a new type by iterating over every key of an existing type and applying a transformation to its value type. ```typescript type Flags<T> = { [K in keyof T]: boolean }; type User = { name: string; age: number }; type UserFlags = Flags<User>; // { name: boolean; age: boolean } ``` **Key point:** `[K in keyof T]` iterates the key union of `T`; add `?` or `readonly` for modifiers, use `as` for key remapping.Shown above the full answer for quick recall.Answer (EN)Image**Mapped types** are a TypeScript feature that creates a new type by iterating over every key of an existing type and applying a transformation to its value type. ## Theory ### TL;DR - Like `Array.prototype.map()` but for object types: input `{name: string, age: number}`, output `{name: boolean, age: boolean}` by changing each value type - Core syntax: `{ [K in keyof T]: NewValueType }` iterates over all keys of `T` - Add `?` to make optional, `readonly` to freeze, `-readonly` or `-?` to remove those modifiers - Key remapping with `as` clause (TypeScript 4.1+): filter or rename keys with template literals - Reach for mapped types when built-in utilities like `Partial<T>` or `Pick<T, K>` do not cover your transformation ### Quick example ```typescript type User = { name: string; age: number; }; // Map each property to boolean type BooleanUser = { [K in keyof User]: boolean; }; // Result: { name: boolean; age: boolean } const perms: BooleanUser = { name: true, age: false }; // valid ``` The `[K in keyof User]` syntax iterates over the key union of `User` and applies `boolean` as the value type for each key. TypeScript generates the new object shape at compile time. Nothing runs at runtime. ### Key difference Mapped types transform every property systematically. A plain type alias just renames a structure without touching it. An intersection type adds properties on top. Mapped types react to the source: add a field to `User` and `BooleanUser` picks it up automatically. That reactivity is what makes them worth using in generic utilities. ### Modifier syntax Four patterns cover most cases: - `[K in keyof T]?: T[K]` makes all properties optional, same behavior as `Partial<T>` - `readonly [K in keyof T]: T[K]` makes all properties readonly, same as `Readonly<T>` - `[K in keyof T]-?: T[K]` removes optional from every property, same as `Required<T>` - `-readonly [K in keyof T]: T[K]` removes readonly, useful as a `Mutable<T>` helper The `-` prefix is the TypeScript way to strip a modifier. You can mix modifiers with value transformations in the same mapping. ### Key remapping with `as` (TypeScript 4.1+) Key remapping lets you rename or filter keys during the mapping: ```typescript type EventHandlers<T> = { [K in keyof T as K extends `on${string}` ? K : never]: T[K]; }; interface ButtonProps { onClick: () => void; label: string; disabled?: boolean; } type Handlers = EventHandlers<ButtonProps>; // Result: { onClick: () => void } ``` The `as` clause runs a conditional on each key. Return `never` to drop the key from the output. Return a template literal type to rename it. This pattern appears often in component libraries that need to separate event callbacks from DOM attributes. ### Homomorphic mapping A mapped type is homomorphic when it maps directly over `keyof T`. In that case TypeScript preserves the original `readonly` and `?` modifiers from the source type, unless you override them explicitly. ```typescript type User = { readonly id: string; name?: string }; type Copy = { [K in keyof User]: User[K] }; // Result: { readonly id: string; name?: string } - modifiers preserved ``` Mappings over arbitrary unions like `[K in string]` are not homomorphic and do not inherit modifiers. That is a common source of confusion when working with index signatures. ### How TypeScript processes mapped types The compiler expands a mapped type during type checking by substituting each key from the union into the mapping clause. For `[K in keyof T]` it walks the key union of `T` and generates a fresh object type literal per substitution. No runtime code is produced. Mapped types are erased like all other type annotations, and the expansion is cached per unique instantiation. ### Common mistakes **Mistake 1: expecting index signatures to survive mapping** ```typescript type Obj = { [key: string]: number }; type Bad = { [K in keyof Obj]?: Obj[K] }; // keyof Obj resolves to string | number // but the index signature itself is gone from the result ``` Fix: intersect the mapped result with the original index signature: ```typescript type Good = { [K in keyof Obj]?: Obj[K] } & { [key: string]: number | undefined }; ``` **Mistake 2: accidentally wiping `readonly` from the source** ```typescript type RUser = { readonly id: string }; type Bad = { [K in keyof RUser]: string }; // id: string - readonly is gone because the explicit value type overrides it ``` To strip `readonly` intentionally use `-readonly`. To keep it, use `RUser[K]` as the value type and let homomorphic preservation do the work. **Mistake 3: nested mappings without recursion** ```typescript // Breaks on nested objects - T[K] is not recursively transformed type ShallowPartial<T> = { [K in keyof T]?: Partial<T[K]> }; ``` Fix with a recursive conditional type: ```typescript type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; ``` **Mistake 4: trying to rename keys without the `as` clause** ```typescript // Cannot rename keys here - this only changes value types type Bad<T> = { [K in keyof T]: string }; // Correct: use `as` with Capitalize or template literals type Good<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: T[K] }; ``` ### Real-world usage - **TypeScript stdlib:** `Partial<T>`, `Required<T>`, `Readonly<T>`, `Pick<T, K>`, and `Record<K, V>` are all mapped types under the hood - **React:** `React.ComponentProps<typeof Button>` uses mapped types to extract HTML attribute types from a component - **TanStack Query:** `UseQueryOptions` maps keys to optional via `{ [K in keyof T]?: T[K] }` - **tRPC:** maps procedure input and output shapes with per-endpoint modifiers - **Zod:** `.partial()` on a schema uses the same `?` modifier pattern internally ### Follow-up questions **Q:** What is the difference between mapped types and conditional types? **A:** Mapped types iterate over keys to produce an object shape. Conditional types (`T extends U ? A : B`) branch on type relationships. They work well together: `{ [K in keyof T]: T[K] extends string ? Uppercase<T[K]> : T[K] }` uses both in one expression. **Q:** What is a homomorphic mapped type? **A:** A mapping that operates directly on `keyof T`. It inherits the original `readonly` and `?` modifiers from `T` unless explicitly overridden. Mappings over arbitrary unions like `[K in string]` are not homomorphic and do not preserve modifiers. **Q:** How do you build a deep `Partial`? **A:** Use a recursive conditional type: `type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;`. The conditional stops recursion at primitive values. **Q:** Can you remap keys with template literal types? **A:** Yes, from TypeScript 4.1. The `as` clause accepts any type expression that resolves to `string | number | symbol | never`, including template literal types like `` `on${Capitalize<string & K>}` ``. **Q:** Implement `Diff<T, U>` that removes keys present in `U` from `T`, preserving original modifiers. **A:** `type Diff<T, U extends keyof any> = { [K in keyof T as K extends U ? never : K]: T[K] };`. Key remapping with a conditional maps excluded keys to `never`, which TypeScript drops from the output. Because it maps over `keyof T` directly it stays homomorphic and preserves modifiers. ## Examples ### Basic: transforming value types ```typescript type Status = { isActive: boolean; isAdmin: boolean; isVerified: boolean; }; // Replace all boolean values with string labels type StatusLabels = { [K in keyof Status]: string; }; // Result: { isActive: string; isAdmin: string; isVerified: string } ``` The shape of `StatusLabels` mirrors `Status` exactly, but every value type is replaced. Adding a new field to `Status` automatically adds it to `StatusLabels`. ### Intermediate: extracting React event handlers ```typescript interface FormProps { onSubmit: (e: Event) => void; onChange: (value: string) => void; placeholder: string; disabled?: boolean; } // Keep only keys that start with "on" type HandlerProps<T> = { [K in keyof T as K extends `on${string}` ? K : never]: T[K]; }; type FormHandlers = HandlerProps<FormProps>; // Result: { onSubmit: (e: Event) => void; onChange: (value: string) => void } ``` The `as K extends \`on${string}\` ? K : never` clause filters the key set. Any key that does not match the pattern is mapped to `never` and dropped. This is the same mechanism behind `React.ComponentProps`. ### Senior: key renaming with conditional exclusion ```typescript type User = { name: string; age: number; readonly id: string; }; // Rename `age` to `viewAge`, drop `id` entirely type SecureUser = { readonly [K in keyof User as K extends 'id' ? never : K extends 'age' ? 'viewAge' : K ]: User[K]; }; // Result: { readonly name: string; readonly viewAge: number } ``` The `readonly` modifier on the mapping applies to all output keys. The original `id` field disappears because its key maps to `never`. I have seen this pattern in API response transformers where server field names need to change before they reach UI components, and doing it at the type level catches mismatches at compile time.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.