Mapped Types
Advanced TypeScript•Section 4 of 4•advanced
Topic Overview
Take your time to understand each concept. Practice with the examples below!
Mapped types are like transformation machines for object types! They let you take an existing type and create a new one by applying changes to every property. It's like using map() on arrays, but for types!
What are Mapped Types? Mapped types iterate over the properties of a type and transform them according to a pattern. They use the [K in keyof T] syntax to loop through each property.
Basic Syntax:
The pattern looks like this:
type NewType = { [K in keyof OldType]: Transformation }
Common Transformations:
- •Make optional:[K in keyof T]?: T[K]
- •Make readonly:readonly [K in keyof T]: T[K]
- •Change type:[K in keyof T]: string
- •Add prefix:[K in keyof T as `get${K}`]: T[K]
Key Remapping (Template Literal Types):
Transform property names themselves:
- •Add prefix:get${Capitalize<K>}
- •Add suffix:${K}Changed
- •Convert case:Uppercase<K>
- •Filter keys:K extends string ? K : never
Real-World Examples:
- •Form states:All fields become FormField<T>
- •API responses:All fields become nullable
- •Event handlers:Convert props to onClick style
- •Getters/Setters:Generate from properties
- •Validation:Add validation rules to each field
Modifiers in Mapped Types:
- •Remove optional:-?
- •Remove readonly:-readonly
- •Add optional:+? (or just ?)
- •Add readonly:+readonly (or just readonly)
Advanced Patterns:
- •Filtering properties by type
- •Creating inverse mappings
- •Nested mapped types
- •Conditional logic inside mappings
Why Mapped Types are Powerful:
- •Transform entire interfaces at once
- •Keep types in sync automatically
- •Build complex utility types
- •Reduce repetitive type definitions
- •Enable powerful abstractions
Built-in Mapped Types:
TypeScript's utility types are mostly mapped types:
- •Partial, Required, Readonly
- •Pick, Omit, Record
- •All built using mapped type syntax!
Example
// Basic mapped typetype Readonly<T> = { readonly [P in keyof T]: T[P];};type Optional<T> = { [P in keyof T]?: T[P];};// Using mapped typesinterface User { id: number; name: string; email: string;}type ReadonlyUser = Readonly<User>;// Result: { readonly id: number; readonly name: string; readonly email: string; }type PartialUser = Optional<User>;// Result: { id?: number; name?: string; email?: string; }// Advanced mapped type with key transformationtype Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];};type UserGetters = Getters<User>;// Result: { getId: () => number; getName: () => string; getEmail: () => string; }// Conditional mapped typetype NonFunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? never : K;}[keyof T];type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;interface Example { id: number; name: string; method(): void;}type ExampleData = NonFunctionProperties<Example>;// Result: { id: number; name: string; }// Template literal types in mapped typestype EventHandlers<T> = { [K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;};type UserEventHandlers = EventHandlers<User>;// Result: { onId: (value: number) => void; onName: (value: string) => void; onEmail: (value: string) => void; }console.log("Mapped types enable powerful type transformations!");