JS Learning

Mapped Types

Advanced TypeScriptSection 4 of 4advanced

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 type
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T> = {
[P in keyof T]?: T[P];
};
// Using mapped types
interface 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 transformation
type 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 type
type 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 types
type 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!");