Mapped types create new types by transforming properties of existing types.

Basic Mapped Type

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

interface User {
  name: string;
  age: number;
}

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }

Common Mapped Types

Partial (Built-in)

type Partial<T> = {
  [P in keyof T]?: T[P];
};

interface User {
  name: string;
  age: number;
}

type PartialUser = Partial<User>;
// { name?: string; age?: number; }

Required (Built-in)

type Required<T> = {
  [P in keyof T]-?: T[P]; // -? removes optional
};

interface User {
  name?: string;
  age?: number;
}

type RequiredUser = Required<User>;
// { name: string; age: number; }

Pick (Built-in)

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

interface User {
  name: string;
  age: number;
  email: string;
}

type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string; }

Custom Mapped Types

Nullable

type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};

interface User {
  name: string;
  age: number;
}

type NullableUser = Nullable<User>;
// { name: string | null; age: number | null; }

Optional

type Optional<T> = {
  [P in keyof T]?: T[P];
};

// Same as Partial, but shown as example

Stringify

type Stringify<T> = {
  [P in keyof T]: string;
};

interface User {
  name: string;
  age: number;
}

type StringifiedUser = Stringify<User>;
// { name: string; age: string; }

Modifiers

Adding readonly

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Removing readonly

type Writable<T> = {
  -readonly [P in keyof T]: T[P];
};

Making optional

type Optional<T> = {
  [P in keyof T]?: T[P];
};

Removing optional

type Required<T> = {
  [P in keyof T]-?: T[P];
};

Key Remapping (TypeScript 4.1+)

Transform keys in mapped types.

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User {
  name: string;
  age: number;
}

type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; }

Filtering Properties

type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

interface User {
  name: string;
  greet(): void;
  age: number;
}

type FunctionProps = FunctionPropertyNames<User>;
// "greet"

Exercises

Exercise 1: Basic Mapped Type

// Create a mapped type 'Optional' that:
// - Makes all properties optional
// - Apply it to an interface

Exercise 2: Readonly Mapped Type

// Create a mapped type 'Immutable' that:
// - Makes all properties readonly
// - Apply it to an interface

Exercise 3: Transform Values

// Create a mapped type 'Promisify' that:
// - Makes all properties return Promises
// - Example: { name: string } becomes { name: Promise<string> }

Exercise 4: Key Remapping

// Create a mapped type 'Setters' that:
// - Transforms keys to setter methods
// - Example: { name: string } becomes { setName: (value: string) => void }

Exercise 5: Filter Properties

// Create a mapped type that:
// - Filters out function properties
// - Keeps only data properties

Best Practices

  1. Use built-in utilities when possible - Partial, Required, etc.
  2. Create reusable mapped types - For common transformations
  3. Document complex mapped types - Explain the transformation
  4. Test mapped types - Ensure they work as expected
  5. Combine with other utilities - Chain transformations

Common Mistakes

  1. Forgetting keyof
// ❌ Wrong
type Bad<T> = {
  [P in T]: T[P]; // Error: T is not a union type
};

// ✅ Correct
type Good<T> = {
  [P in keyof T]: T[P];
};
  1. Wrong modifier syntax
// ❌ Wrong
type Bad<T> = {
  [P in keyof T]?: T[P]; // This makes optional, not readonly
};

// ✅ Correct for readonly
type Good<T> = {
  readonly [P in keyof T]: T[P];
};
  1. Not understanding distribution
// Mapped types work on object types
// They don't distribute over unions the same way conditional types do

Next Steps

Learn about Conditional Types.