TypeScript Mapped Types

· 4 min read · Updated March 7, 2026 · advanced
mapped-types types generics advanced

Mapped types are one of TypeScript’s most powerful type-system features. They let you transform existing types into new ones by iterating over their keys. If you’ve ever wanted to make all properties optional, readonly, or nullable, mapped types are the tool for the job.

In this tutorial, you’ll learn how mapped types work, how to use built-in mapped types, and how to create your own.

What Are Mapped Types?

A mapped type creates a new type by transforming each property of an existing type. The basic syntax looks like this:

type Mapped<T> = {
  [K in keyof T]: T[K];
};

Let’s break this down:

  • K in keyof T — iterates over each key in type T
  • T[K] — the corresponding value type for each key

This example creates an exact copy of T. But mapped types become powerful when you add transformations.

Basic Mapping Transformations

Making All Properties Optional

type User = {
  id: number;
  name: string;
  email: string;
};

type PartialUser = {
  [K in keyof User]?: User[K];
};

// Result:
// type PartialUser = {
//   id?: number | undefined;
//   name?: string | undefined;
//   email?: string | undefined;
// }

Making All Properties Readonly

type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

// Result:
// type ReadonlyUser = {
//   readonly id: number;
//   readonly name: string;
//   readonly email: string;
// }

Making All Properties Nullable

type NullableUser = {
  [K in keyof User]: User[K] | null;
};

// Result:
// type NullableUser = {
//   id: number | null;
//   name: string | null;
//   email: string | null;
// }

TypeScript provides these transformations as built-in utility types: Partial<T>, Readonly<T>, Required<T>, and Pick<T, K>.

Key Remapping

TypeScript 4.1+ lets you remap keys using the as clause:

type User = {
  firstName: string;
  lastName: string;
  age: number;
};

// Add "Model" suffix to each key
type UserModels = {
  [K in keyof User as `${K}Model`]: User[K];
};

// Result:
// type UserModels = {
//   firstNameModel: string;
//   lastNameModel: string;
//   ageModel: number;
// }

This is incredibly useful for creating derivative types.

Filtering Keys with Conditional Types

Combine mapped types with conditional types to filter properties:

type Props = {
  name: string;
  age: number;
  visible: boolean;
  disabled: boolean;
};

// Only keep boolean properties
type BooleanProps<T> = {
  [K in keyof T as T[K] extends boolean ? K : never]: T[K];
};

type PropsFilter = BooleanProps<Props>;
// Result: type PropsFilter = { visible: boolean; disabled: boolean; }

The never type removes keys from the resulting type entirely.

Practical Examples

Creating a Form Type from a Schema

type DatabaseUser = {
  id: number;
  username: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
  passwordHash: string;
};

// Create a form type (exclude internal/db fields)
type UserForm = {
  [K in keyof DatabaseUser as 
    Exclude<K, "id" | "createdAt" | "updatedAt" | "passwordHash">]: DatabaseUser[K]
};

// Result:
// type UserForm = {
//   username: string;
//   email: string;
// }

Getters and Setters

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

type Setters<T> = {
  [K in keyof T as `set${Capitalize<K & string>}`]: (value: T[K]) => void;
};

type PropertyAccessors<T> = Getters<T> & Setters<T>;

type UserAccessors = PropertyAccessors<{ name: string; age: number }>;
// Result includes getName(), setName(), getAge(), setAge()

Converting Snake Case to Camel Case

type SnakeToCamel<S extends string> = 
  S extends `${infer T}_${infer U}`
    ? `${T}${Capitalize<SnakeToCamel<U>>}`
    : S;

type SnakeToCamelObject<T> = {
  [K in keyof T as SnakeToCamel<K & string>]: T[K];
};

type ApiResponse = {
  user_id: number;
  first_name: string;
  last_name: string;
  created_at: string;
};

type CamelResponse = SnakeToCamelObject<ApiResponse>;
// Result:
// type CamelResponse = {
//   userId: number;
//   firstName: string;
//   lastName: string;
//   createdAt: string;
// }

Modifiers: readonly and ?

You can add or remove modifiers using + and - prefixes:

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

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

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

This is how TypeScript’s built-in utility types work under the hood.

Built-in Mapped Types

TypeScript includes several mapped types in its standard library:

Utility TypeDescription
Partial<T>Makes all properties optional
Required<T>Makes all properties required
Readonly<T>Makes all properties readonly
Pick<T, K>Selects specific properties
Omit<T, K>Excludes specific properties
Record<K, T>Creates type with keys K and values T

These are all implemented using mapped types.

Summary

Mapped types let you transform types by iterating over their keys. Key concepts:

  • Use [K in keyof T] to iterate over all keys
  • Use as to remap keys (TypeScript 4.1+)
  • Use conditional types with never to filter keys
  • Use +/- prefixes to add or remove modifiers

Mapped types are the foundation for many TypeScript utility types. Once you master them, you’ll be able to create sophisticated type transformations that make your code more type-safe.

What’s Next?

In the next tutorial, we’ll explore Conditional Types, which let you create types that depend on other types—essential for advanced TypeScript patterns.