Mapped types in TypeScript

A mapped type is a way to create new types based on existing ones by transforming their properties

As it works on a single type and typically narrows or limits the properties it works in a way that is opposite to a union or intersection type.

Mapped types are an example of when type is preferable to interface, since you cannot generate mapped types as interfaces, although you may generate a mapped type from an interface.

For the demonstrations we will use the following type as the example that we will map from:

type Person = {
  name: string;
  age: number;
  city: string;
  country: string;
};

Main varieties of mapped types

Read only

Creates a type with all properties of the given type set to readonly:

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

type ReadonlyPerson = Readonly<Person>;

This is equivalent to:

type ReadonlyPerson = {
  readonly name: string;
  readonly age: number;
  readonly city: string;
  readonly country: string;
};

Partial

Creates a type with all properties of the given type set to optional.

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

type PartialPerson = Partial<Person>;

This is equivalent to:

type PartialPerson = {
  name?: string;
  age?: number;
  city?: string;
  country?: string;
};

Pick

This is useful when you want to create a new type based on a subset of properties from an existing type.

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

type PersonNameAndAge = Pick<Person, "name" | "age">;

This is equivalent to:

type PersonNameAndAge = {
  name: string;
  age: number;
};

Record

Creates a type with keys of the given type and values of the specified type. It is a way of shoehorning keys from an existing type with new values.

Basic syntax:

Record<Keys, ValueType>;

Example:

type UserRole = "admin" | "user" | "guest";

// Create a Record type to store user role descriptions
type UserRoleDescriptions = Record<UserRole, string>;

// Create an object that implements the UserRoleDescriptions type
const roleDescriptions: UserRoleDescriptions = {
  admin: "Has full access to the system",
  user: "Can access limited resources",
  guest: "Can view public resources only",
};

Here is a simpler example

type Person = {
  name: string;
  nationality: string;
};

type PersonInstance = Record<string, Person>;
const thomasInstance: PersonInstance = {
  thomas: {
    name: "Thomas",
    nationality: "British",
  },
};

Exclude

Creates a type by excluding specific properties from the given type.

type Exclude<T, U> = T extends U ? never : T;

type KeysWithoutAge = Exclude<keyof Person, "age">;

This is equivalent to:

type KeysWithoutAge = "name" | "city" | "country";

Extract

Creates a type by extracting specific properties from the given type. Basically the opposite operation to Exclude.

type Extract<T, U> = T extends U ? T : never;

type NameKey = Extract<keyof Person, "name">;

This is equivalent to:

type NameKey = "name";

Non-nullable

Creates a type by removing null and undefined from the given type.

type NonNullable<T> = T extends null | undefined ? never : T;

type NullablePerson = {
  name: string | null;
  age: number | null;
  city: string | null;
  country: string | null;
};

type NonNullablePerson = NonNullable<NullablePerson>;

Equivalent to:

type NonNullablePerson = {
  name: string;
  age: number;
  city: string;
  country: string;
};