---
title: TypeScript Mapped Types in Depth
description: This post provides an in-depth guide on how to derive mapped types in TypeScript.
slug: typescript-mapped-types
authors: abdullah_numan
tags: [typescript]
image: https://refine.ams3.cdn.digitaloceanspaces.com/blog/2024-02-29-ts-map/social.png
hide_table_of_contents: false
---

## Introduction

This post provides an in-depth guide on how to derive mapped types in TypeScript.

A mapped type in TypeScript is a new type derived from a base type with the help of a custom type mapper utility.

Deriving new types with a custom type mapper is a common practice that ensures DRY (Dont' Repeat Yourself) code in a TypeScript codebase. There are a number of ways by which new types are derived in TypeScript and custom mapping is one such technique.

A TS type mapper builds on TypeScript's index signature syntax to transform and produce a new type from a union type.

In this post, we explore how to define and use custom-type mapper utilities with examples that derive new types from source types. We first understand underlying TypeScript concepts that entail deriving mapped types: the TypeScript index signature syntax, union of types, and the `in` and `keyof` operators. We elaborate and see how these concepts can be combined to define custom generic type mapper utilities that map passed-in type parameters into reusable type definitions. We also dig deeper and learn about ways of remapping and applying useful transformations to derived types with the help of the TypeScript `as` operator, and native type utilities like `Capitalize<>` and `Exclude<>`.

Steps to be covered in this post:

- [What are Mapped Types in TypeScript?](#what-are-mapped-types-in-typescript)
- [How are Mapped Types Created?](#how-are-mapped-types-created)
- [Building Blocks of a Custom TypeScript Type Mapper Utility](#building-blocks-of-a-custom-typescript-type-mapper-utility)
- [TypeScript Mapped Types with Generic Type Mappers](#typescript-mapped-types-with-generic-type-mappers)

## What are Mapped Types in TypeScript?

A mapped type in TypeScript is a new type derived from a source type. Mapped types can be trivial as well as complex ones manipulated by a generic type mapper. In a generically mapped type, the keys of the new type are derived by mapping to the keys of the source type.

Below are examples of a generically mapped type and its associated source type:

```ts
// A generically mapped type:
type TThemeSetters = {
  setDefaultTheme: () => void;
  setOrientalTheme: () => void;
  setVikingTheme: () => void;
  setSpringTheme: () => void;
  setSantaTheme: () => void;
};

// Source type from which the above mapped type was derived:
type IThemes = {
  default: {};
  oriental: {};
  viking: {};
  spring: {};
  santa: {};
};
```

As we can see, each of the keys in the derived type have been mapped from their corresponding source type keys.

### How are Mapped Types Created?

TypeScript mapped types are created via a custom type mapper utility. The mapper utility is defined in a way that it helps customize and transform the mapped type according to new type requirements.

## What is a TS Custom Type Mapper Utility?

A custom-type mapper utility is usually a generic type definition that derives a new type from a source type. It takes in the source type as a parameter, performs necessary transformations, and then produces a new separate version with the desired mapped type.

During the transformation, the keys and values are transformed as needed. Common transformations made in a type mapper utility include changing key name patterns, the shape of their values, filtering keys, adding keys, making them `readonly`, making properties optional, etc.

The custom generic type mapper for the above example looks like this:

```ts
type TThemeSetterMapper<Source> = {
  [Property in keyof Source as `set${Capitalize<
    string & Property
  >}Theme`]: () => void;
};
```

### How Do Custom TS Type Mappers Work?

As you can see in the above example, a custom type mapper leverages TypeScript's [index signature syntax](https://www.typescriptlang.org/glossary#index-signatures) to build the derived type. It uses the [`keyof`](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html) type operator to create a union of keys from the source type, the [`in`](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing) narrowing operator to loop through the union keys, and assigns types to the values of each key.

Where necessary, it uses the [`as`](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) type assertion clause to perform remapping, transformations, and conditional mapping.

## Building Blocks of a Custom TypeScript Type Mapper Utility

Before we get into the mechanics of the above examples, in this section, we spend some time to get a fair understanding of the above-mentioned underlying building blocks that make a custom-type mapper utility.

### TypeScript Custom Type Mapper Building Blocks: The Index Signature Syntax

TypeScript index signature syntax forms the most important component of a custom type mapper. An example looks like this:

```ts
type TThemeAction = {
  [key: string]: {
    start: string;
    end: string;
  };
};
```

In this most basic example, we are allowing an arbitrary string property to be set inside a theme action object. The value has to be an object with `start` and `end` properties, both of which must have `string` values.

Using index signatures this way, however, is unconstrained, as we can add as many properties as we possibly could -- something that is not helpful for type specificity.

### TS Custom Type Mapper Building Blocks: Union of Types and the `in` Operator

TypeScript custom type mappers utilize a union of types to enforce type specificity by limiting properties to members of the union. The union members are then looped over with the `in` operator and mapped to members of the new type:

```ts
type TThemeName = "default" | "oriental" | "viking" | "spring" | "santa";

type TThemeAction = {
  [name in TThemeName]: {
    start: string;
    end: string;
  };
};
```

In the example above, we have the `TThemeName` type, which is a union of `string`s that limits the mapped type's keys to the union members. So, the index signature with the `TThemeName` union maps to the following `TThemeAction` type:

```ts
type TThemeAction = {
  default: {
    start: string;
    end: string;
  };
  oriental: {
    start: string;
    end: string;
  };
  viking: {
    start: string;
    end: string;
  };
  spring: {
    start: string;
    end: string;
  };
  santa: {
    start: string;
    end: string;
  };
};
```

As you can see, mapping types with index signatures is proving already useful in deriving an object type with crowded keys. Thanks to TypeScript custom type mapping, we are able to easily produce an oversized shape which we'd otherwise be overwhelmed with.

### TypeScript Custom Type Mapper: Union of Keys with `keyof`

Mapping types in TypeScript is more useful when we have a complex source type. One example would be theme definitions that typically involve a myriad of nested properties:

```ts
interface IThemes {
  default: {
    /*...nested theme stuff here */
  };
  oriental: {
    /*...nested theme stuff here */
  };
  viking: {
    /*...nested theme stuff here */
  };
  spring: {
    /*...nested theme stuff here */
  };
  santa: {
    /*...nested theme stuff here */
  };
}

type TThemeName = keyof IThemes; // Equivalent to: "default" | "oriental" | "viking" | "spring" | "santa";

type TThemeAction = {
  [name in TThemeName]: {
    start: string;
    end: string;
  };
}; // Produces the same TThemeAction map as in the previous example with plain union type
```

In this case, we have a sophisticated `IThemes` type that usually involves nested members, which pose a challenge to derive or transform types manually or using TypeScript's native utilities.

Notice the `TThemeName` type this time. And how we are able to get the necessary union type from the source type keys (`IThemes`) with the `keyof` operator. Also notice how it produces the same `TThemeAction` type as in the previous occasion.

## TypeScript Mapped Types with Generic Type Mappers

Okay, so far we have been mapping the source type directly. Custom-type mappers are efficient when they are DRY and reusable with a generic definition. So, in this section, we explore examples of generic custom-type mappers.

### What are TS Custom Generic Type Mappers?

Generic type mappers in TypeScript accept the source type as a parameter and any additional parameters to perform necessary transformations on the source type's keys and values.

### How to Define a TS Custom Generic Type Mapper Utility

Below is an example of how to define a generic version of the above `TThemeAction` type definition:

```ts
type TAction<Actors> = {
  [Property in keyof Actors]: {
    start: string;
    end: string;
  };
};

type TThemeAction = TAction<IThemes>; // Produces the same `TThemeAction` mapped type
```

Here, we have refactored the type mapper to a generic that takes the source type (`Actor`) as a parameter and then uses it to make the transformations. This makes the type mapper reusable.

We can make the mapper more versatile by passing the value of the key as parameter as well:

```ts
type IThemes = {
  default: {};
  oriental: {};
  viking: {};
  spring: {};
  santa: {};
};

type TAction<Actors, Value> = {
  [Property in keyof Actors]: Value;
};

type TThemeAction = TAction<IThemes, { start: string; end: string }>; // Produces the same `TThemeAction` mapped type
```

This time around, we are passing both the source type and the shape of the value of each key as params. This way, our type mapper is more adjustable to diverse use cases with various target shapes.

### TypeScript Type Mapper Utility vs TS Mapped Type: The Difference

It should be obvious by now that both the type mapper utility and the mapped type derived from it are TypeScript types. In other words, they are expressed as types.

One important difference is that TypeScript **type mappers are type definitions**, while mapped types are type assignments declared from those definitions.

### TS Custom Type Mapper: Remapping with the `as` Clause

With a TypeScript type mapper, we can apply different sorts of transformations to the source keys. We can change the pattern of key identifiers, filter, or add new keys. We use the TypeScript `as` clause to modify the key sets and their identifiers.

For example, in a scenario where we need a type that describes the setter functions of all the theme items in the `IThemes` object above, we can easily derive new identifiers that produce a theme setter type by transforming the key identifiers. As with the following mapper:

```ts
type IThemes = {
  default: {};
  oriental: {};
  viking: {};
  spring: {};
  santa: {};
};

type TThemeSetterMapper<Source> = {
  [Property in keyof Source as `set${Capitalize<
    string & Property
  >}Theme`]: (theme: {}) => void;
};

type TThemeSetters = TThemeSetterMapper<IThemes>;
// Produces the following type:
/*
type TThemeSetters = {
    setDefaultTheme: (theme: {}) => void;
    setOrientalTheme: (theme: {}) => void;
    setVikingTheme: (theme: {}) => void;
    setSpringTheme: (theme: {}) => void;
    setSantaTheme: (theme: {}) => void;
};
*/
```

We can refactor this even further to make the setter mapper more generic:

```ts
type TSetterMapper<Source, SetterTarget> = {
  [Property in keyof Source as `set${Capitalize<string & Property>}${Capitalize<
    string & SetterTarget
  >}`]: (theme: {}) => void;
};

type TThemeSetters = TSetterMapper<IThemes, "Theme">; // Produces the same mapped type of theme setters as above
```

Notice in both occasions, we are transforming the key identifiers with TypeScript's `Capitalize<>` utility. Native TypeScript transformation utilities are instrumental in deriving custom-mapped types.

### TypeScript Mapped Types: Key Filtering

While deriving a mapped type, we can apply key filtering with the `Exclude<>` utility.

For example, we can remove keys if we need to. Below is an example that let's us derive a mapped type with keys of our choice removed:

```ts
type TFilterMapper<Source, Keys> = {
  [Property in keyof Source as Exclude<Property, Keys>]: Source[Property];
};

type TFilteredThemes = TFilterMapper<IThemes, "santa" | "spring">;
```

Here, we are using the TypeScript `Exclude<>` utility to filter the keys of our choice from the union of keys generated to `Source`. Passing `Keys` as a parameter to the mapper allows us to selectively pass the keys to be excluded in the derived type. The resulting type has `santa` and `spring` removed from its shape:

```ts
type TFilteredThemes = {
  default: {};
  oriental: {};
  viking: {};
};
```

Notice again that, the `TFilterMapper` is reusable. This means we can filter any source type to exclude any keys. For example, it can be used for filtering other source types:

```ts
type TNotificationOptions = {
  desktop: boolean;
  email: boolean;
  mobile: boolean;
};

type TFilteredNotificationOptions = TFilterMapper<
  TNotificationOptions,
  "mobile"
>;
// Produces the following type:
/*
type TFilteredNotificationOptions = {
    desktop: boolean;
    email: boolean;
};
*/
```

Notice, in this case, we have directly mapped the value to its original type with `Source[Property]`.

### When to Use TS Custom Type Mappers?

TypeScript mapped types are needed to derive types from key application entity types, such as configuration objects, backend API data shapes.

The general intent is to provide type safety to mission-critical application entities and reduce runtime errors ahead of time. In terms of developer experience, mapping complex types to newer derivations with custom-type mappers provides convenience and development efficiency.

Custom TypeScript Mapped Types are used for:

- Deriving new types with transformed keys.
- Producing new types with filtered keys.
- Deriving mapped types with added keys.
- Mapping to new types with transformed values.
- Defining reusable generic utilities.
- Deriving safer types with `readonly` properties.
- Deriving partial types with optional properties.

## Summary

In this post, we explored in significant depth the use of TypeScript mapped types with custom type mappers. We first learned what mapped types and custom type mappers are. We then we examined the index signature syntax, union types, the `in` and `keyof` operators, and how they are used in a custom type mapper in TypeScript.

We then explored with examples of how to derive mapped types from complex `IThemes` type with the help of custom defined generic type mapper utilities. Towards the end, we covered examples that demonstrate how to transform keys and their values with the TypeScript `as` clause, `Exclude<>`, and `Capitalize<>` utilities.
