Mapped Types
HardMapped types let you create new types by transforming each property of an existing type. They use the `in keyof` syntax to iterate over property keys and apply transformations such as making properties optional, readonly, or changing their value types. All of TypeScript's built-in utility types like Partial, Required, and Readonly are implemented as mapped types.
Interactive Visualization
Key Points
- Syntax: `{ [K in keyof T]: TransformedType }` iterates over all keys of T
- Modifiers `?` (optional) and `readonly` can be added or removed with `+` / `-`
- Key remapping with `as` lets you rename or filter keys: `{ [K in keyof T as NewKey]: T[K] }`
- Template literal types in key remapping enable pattern-based key transformations
- Mapped types can be combined with conditional types for powerful type-level logic
- The `-?` modifier removes optionality (used by `Required<T>` internally)
Code Examples
Basic Mapped Types
// Make all properties optional (this is how Partial works) type MyPartial<T> = { [K in keyof T]?: T[K] } // Make all properties required (remove ?) type MyRequired<T> = { [K in keyof T]-?: T[K] } // Make all properties readonly type MyReadonly<T> = { readonly [K in keyof T]: T[K] } // Make all property values nullable type Nullable<T> = { [K in keyof T]: T[K] | null } interface Config { host: string port: number debug?: boolean } type NullableConfig = Nullable<Config> // { host: string | null; port: number | null; debug?: boolean | null }
Mapped types iterate over each key in a type and produce a new type. The `in keyof` clause enumerates all property keys, and you can transform both keys and values.
Key Remapping with `as`
// Prefix all keys with "get" type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] } interface Person { name: string age: number } type PersonGetters = Getters<Person> // { getName: () => string; getAge: () => number } // Filter keys by value type — keep only string properties type StringKeysOnly<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] } interface Mixed { name: string age: number email: string active: boolean } type StringProps = StringKeysOnly<Mixed> // { name: string; email: string }
Key remapping with `as` lets you transform or filter property keys. Mapping a key to `never` removes it from the output type, which is how you filter properties by their value type.
Event Handler Map Pattern
// Real-world pattern: generate event handler types from an event map interface EventMap { click: { x: number; y: number } focus: { target: HTMLElement } submit: { data: FormData } } type EventHandlers<T> = { [K in keyof T as `on${Capitalize<string & K>}`]: (event: T[K]) => void } type Handlers = EventHandlers<EventMap> // { // onClick: (event: { x: number; y: number }) => void // onFocus: (event: { target: HTMLElement }) => void // onSubmit: (event: { data: FormData }) => void // } // Make specific keys optional, rest required type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>> type UserWithOptionalEmail = PartialBy<Person & { email: string }, 'email'>
Mapped types with template literal keys enable powerful patterns like auto-generating event handler interfaces from event definitions. This is a common pattern in UI frameworks.
Common Mistakes
- Forgetting the `string &` intersection when using template literal key remapping (K may be symbol)
- Not realizing that mapped types distribute over union types in `keyof`
- Confusing the `-?` modifier (remove optional) with just `?` (add optional)
- Writing overly complex mapped types that are difficult to debug
- Not knowing that homomorphic mapped types preserve modifiers from the original type
Interview Tips
- Be able to implement Partial, Required, and Readonly from scratch — they are all mapped types
- Key remapping with `as` is an advanced feature that impresses interviewers
- Explain how `never` in key position removes a property from the mapped type
- Mention that built-in utility types are mapped types under the hood