Conjuring TypeScript's Magic with Mapped Types
Tags: typescript, programming, tutorial, types
Published: 2023-06-27 17:58:58

---
title: Conjuring TypeScript's Magic with Mapped Types
published: true
date: 2023-06-23 00:00:00 UTC
tags: 
  - typescript
  - programming
  - tutorial
  - types
canonical_url: https://www.susanpotter.net/software/conjuring-typescripts-magic-with-mapped-types/
image_url: https://www.susanpotter.net/images/software/typescript-mapped-types-example.png
---

# Table of Contents

1.  [TypeScript: The Savvy Sidekick of JavaScript](#org8be568b)
2.  [Why Mapped Types Matter](#org1c09845)
3.  [Mapped Types Syntax and Basic Plumbing](#orgabf9569)
    1.  [The Enchanting Syntax: Casting the Spell](#orgc104ca2)
    2.  [The Components of Mapped Types: Unmasking the Wizards](#org221051a)
4.  [A Deeper Dive!](#org0a577c9)
    1.  [Modifying Property Modifiers: Bending the Rules of Nature](#org355e500)
    2.  [Transforming Property Types: A Symphony of Type Transformations](#org7357742)
5.  [Tips, Tricks, and Best Practices: Unleashing the Magic Safely](#org7c21d12)
    1.  [Tips for Working with Mapped Types: Navigating the Magical Realm](#org85ed905)
    2.  [Best Practices and Guidelines: Taming the Magic for Large-Scale Projects](#orgedc7685)
6.  [Wrapping it up](#orgb294c29)


In this article, we&rsquo;ll take a whirlwind tour through the fascinating world of TypeScript and its type system. Hold onto your hats and get ready to unleash the power of mapped types!


<a id="org8be568b"></a>

## TypeScript: The Savvy Sidekick of JavaScript

Buckle up, folks! TypeScript swoops in like a suave sidekick, adding an extra layer of pizzazz to the world of JavaScript. With TypeScript, you get to catch those pesky bugs before they even think about causing chaos in your code.

It&rsquo;s time to introduce you to the heroes of our story: mapped types! These are the true sorcerers of TypeScript especially when used in conjunction with [keyof](https://www.susanpotter.net/software/typescripts-keyof-operator-and-possible-uses/) and [conditional types](https://www.susanpotter.net/software/using-conditional-types-in-typescript/). They wield the power to transform and shape types with a flick of their magical wands. Mapped types allow you to redefine existing types, making them dance to your tune.


<a id="org1c09845"></a>

## Why Mapped Types Matter: The Plot Thickens

Mapped types give you the power to streamline your code, enhance its clarity, and catch those sneaky bugs before they wreak havoc. They enable you to write robust, maintainable code that makes your fellow developers nod in gratitude (when used just right).

To illustrate the utility of mapped types we will show how to **use** a mapped type commonly used in TypeScript already: `Pick`.

Let us start with the trusted `Person` type example developers overuse ad nauseam to show what it does:

```typescript
    type Person = {
      name: string;
      age: number;
      address: string;
      email: string;
    };
```
Sometimes we only want to pass a subset of the `Person` type&rsquo;s properties values to functions. In JavaScript we either pass the entire object value regardless or we pass the properties one-by-one as separate arguments.

In TypeScript we can compute a new type that plucks (or &ldquo;picks&rdquo;) a specific subset of properties names to collate in one type definition.

In the example below we pluck (or &ldquo;pick&rdquo;) the `name` and `age` properties from the `Person` type defined above.

```typescript
    // Behold, the power of Pick!
    type PersonBasicInfo =
      Pick<Person, 'name' | 'age'>;
```
The type computed by `Pick<Person, 'name' | 'age'>` looks like this:
```typescript
    {
      name: string;
      age: number;
    }
```
With the power of `Pick` we summoned the `PersonBasicInfo` type containing only the `name` and `age` properties.


<a id="orgabf9569"></a>

## Mapped Types Syntax and Basic Plumbing

It&rsquo;s time to decode the incantations and unravel the key components that make mapped types spellbinding.


<a id="orgc104ca2"></a>

### The Enchanting Syntax: Casting the Spell

We define mapped types using angle brackets `< >` and curly braces `{ }`. Let&rsquo;s demystify the basic syntax step-by-step:

```typescript
    type MappedType = {
      [Property in ExistingType]: NewProperty
    };
```
`MappedType`
: This is the name you give to your created mapped type. It can be any valid type name you choose.

`[Property in ExistingType]`
: This is the magical syntax incantation that sets up your mapped type. `Property` represents each property in the existing type that you want to manipulate. `ExistingType` is the original type from which you&rsquo;re deriving the mapped type.

`NewProperty`
: This is where your creativity comes into play. You define the transformed version of each property in the mapped type.


<a id="org221051a"></a>

### The Components of Mapped Types: Unmasking the Wizards


<a id="org36be86c"></a>

#### Property: The Building Block of Magic

In this example, we&rsquo;ll transform the properties of an existing type, `Person`, into optional properties using mapped types:
```typescript
    type Person = {
      name: string;
      age: number;
      address: string;
    };
    
    type OptionalPerson = {
      [Property in keyof Person]?: Person[Property]
    };
```
`Person`
: Our existing type with properties `name`, `age`, and `address`.

`OptionalPerson`
: The name of our mapped type. We use the `keyof` operator to iterate over each property in `Person` and create optional properties in `OptionalPerson`.

The resulting type looks like the following:
```typescript
    type OptionalPerson = {
      name?: string | undefined;
      age?: number | undefined;
      address?: string | undefined;
      email?: string | undefined;
    };
```
Examples of valid constructions of this type include:
```typescript
    // Note: Shakespeare doesn't have an email
    const shakespeare: OptionalPerson = {
      name: "William Shakespeare",
      age: 52,
      address: "Stratford-upon-Avon, England",
    };
    
    // After dying Shakespeare's address changed
    const deathChanges: OptionalPerson = {
      address: "Church of the Holy Trinity, Stratford-upon-Avon",
    };
    
    const updatedShakespeare: OptionalPerson = {
      ...shakespeare,
      ...deathChanges
    };
```

<a id="org8ac3f95"></a>

#### ExistingType: The Source of Power

Let&rsquo;s explore another example that transforms the property types of an existing type:
```typescript
    type Person = {
      name: string;
      age: number;
    };
    
    type PersonWithOptionalProps = {
      [Property in keyof Person]: Person[Property] | undefined
    };
```
`Person`
: Our existing type with properties `name` and `age`.

`PersonWithOptionalProps`
: Our mapped type that retains the same properties as `Person`, but with transformed property types. We use the `keyof` operator to iterate over each property in `Person` and create a union type with `undefined`.

You might be wondering what is the difference between `OptionalPerson` and `PersonWithOptionalProps`? Let&rsquo;s look at the computed type definition:
```typescript
    type PersonWithOptionalProps = {
        name: string | undefined;
        age: number | undefined;
        address: string | undefined;
        email: string | undefined;
    };
```
Note that the property names do not have the `?` suffix. What does this mean in pracitce? Let&rsquo;s try to set the type to the object literal set to the `shakespeare` constant above:
```typescript
    const shakespeare2: PersonWithOptionalProps = {
      name: "William Shakespeare",
      age: 52,
      address: "Stratford-upon-Avon, England",
    }; // Errors see message below
```
Now we get an error! Let&rsquo;s take a look:
```typescript
    Property 'email' is missing in type
             '{ name: string;
                age: number;
                address: string;
              }'
    but required in type 'PersonWithOptionalProps'.
```
So what this is saying is that we do need to set the missing `email` property. To model our requirements, we can set it to `undefined` to denote that `shakespeare2` has no `email` property value.
```typescript
    const shakespeare2: PersonWithOptionalProps = {
      name: "William Shakespeare",
      age: 52,
      address: "Stratford-upon-Avon, England",
      email: undefined,
    };
```
The above now typechecks and the original error applies to any missing property. We needed to explicitly set the missing properties from `Person` to `undefined`.


<a id="org9565c03"></a>

#### Modifying Properties: Infusing Your Magic

Let&rsquo;s delve into an example where we omit the modifiers of properties:
```typescript
    type ReadonlyPerson = {
      readonly name: string,
      readonly age: number,
      readonly address: string,
      readonly email: string,
    };
    
    type MutablePerson = {
      -readonly [Property in keyof ReadonlyPerson]: ReadonlyPerson[Property]
    };
```
`Person`
: Our existing type with properties `name` and `age`.

`MutablePerson`
: Our mapped type that mirrors the properties of `Person`, but removes the `readonly` modifier. We use the `-readonly` syntax to update the property modifiers within the mapped type.

`MutablePerson` computes to:
```typescript
    type MutablePerson = {
        name: string;
        age: number;
        address: string;
        email: string;
    };
```
No `readonly`, mom!


<a id="orgb35c322"></a>

#### Adding Properties

Let&rsquo;s explore one last example to showcase the versatility of mapped types:
```typescript
    type Circle = {
      radius: number;
    };
    
    type Cylinder = {
      radius: number;
      height: number;
    };
    
    type CalculateVolume<T> = {
      [Property in keyof T]: T[Property];
    } & { volume: number };
    
    function calculateCylinderVolume(
      cylinder: Cylinder
    ): CalculateVolume<Cylinder> {
      const volume =
        Math.PI * cylinder.radius ** 2 * cylinder.height;
      return { ...cylinder, volume };
    }
```
`Circle`
: Our existing type representing a circle with a `radius` property.
`Cylinder`
: Another existing type representing a cylinder with `radius` and `height` properties.
`CalculateVolume<T>`
: Our mapped type that extends any existing type `T`. It retains the properties of `T` and adds a new `volume` property of type `number`.
`calculateCylinderVolume`
: A function that takes a `Cylinder` object, calculates its volume, and returns a `CalculateVolume<Cylinder>` object with the original properties of `Cylinder` and the newly added `volume` property.

With these examples, you&rsquo;ve witnessed the magic of mapped types. We have manipulated properties, modified modifiers, transformed property types and added new properties.


<a id="org0a577c9"></a>

## A Deeper Dive!

In this section, we explore the mechanics of property transformation and mapped types to reshape our types with a dash of enchantment.


<a id="org355e500"></a>

### Modifying Property Modifiers: Bending the Rules of Nature


<a id="orgd24479d"></a>

#### Making Properties Optional: Partial type

Imagine a world where properties have the freedom to choose their destiny, where they can be present or absent at their whim. Enter the wondrous `Partial` type! Let&rsquo;s demystify the derivation of `Partial` from first principles:
```typescript
    type Partial<T> = {
      [Property in keyof T]?: T[Property]
    };
```
`Partial<T>`
: computes a new type that mirrors the original type `T`, but grants properties the ability to become optional using the `?` modifier. It&rsquo;s like giving properties a ticket to the land of freedom.

Let&rsquo;s witness the power of `Partial` in action:
```typescript
    interface Wizard {
      name: string;
      spells: string[];
    }

    type PartialWizard = Partial<Wizard>;

    const wizard: PartialWizard = {}; // Property "name" and "spells" become optional
```
`PartialWizard`
: Our transformed type derived from `Wizard` using the `Partial` mapped type. Now, properties like `name` and `spells` have the choice to be present or absent, granting flexibility and easing our coding journey.


<a id="org95b8c79"></a>

#### Making Properties Read-Only: `Readonly` type

In the land of code, where properties roam free, some properties prefer to stand tall and unchangeable, like statues of wisdom. Enter the majestic `Readonly` type, which bestows the power of immutability upon properties. Let&rsquo;s unlock the secrets of `Readonly`:
```typescript
    type Readonly<T> = {
      readonly [Property in keyof T]: T[Property]
    };
```
`Readonly<T>`
: The alchemical mixture that creates a new type with the same properties as the original `T`, but marked as `readonly`. It&rsquo;s like encasing properties in an unbreakable spell, ensuring they remain untouchable.

Behold the might of `Readonly` in action:
```typescript
    interface Potion {
      name: string;
      ingredients: string[];
    };
    
    const potion: Potion = {
      name: "Elixir of Eternal Youth",
      ingredients: ["Unicorn tears", "Moonlight essence"],
    };
    
    potion.name = "Forbidden Potion"; // Works
    console.debug(potion.name); // prints "Forbidden Potion"
    
    type ReadonlyPotion = Readonly<Potion>;
    
    const ropotion: ReadonlyPotion = {
      name: "Elixir of Eternal Youth",
      ingredients: ["Unicorn tears", "Moonlight essence"],
    };
    
    ropotion.name = "Forbidden Potion"; // Error! Property "name" is read-only
```
`ReadonlyPotion`
: Our transformed type created from `Potion` using the `Readonly` mapped type. Now, properties are guarded against any attempts to change them. This ensures their immutability and preserves their original value.


<a id="org53d2b5b"></a>

#### Excluding Properties: `Exclude` type

Sometimes we need to separate the chosen from the unwanted, to exclude certain elements from our type sorcery. Enter the extraordinary `Exclude` type, capable of removing specific types from a union. Let&rsquo;s uncover the essence of `Exclude`:
```typescript
    type Exclude<T, U> = T extends U ? never : T;
```
`Exclude<T, U>`
: The spell that removes types present in U from the union of types T. Like a forcefield that shields our types from unwanted members.

Let&rsquo;s witness the might of `Exclude`:
```typescript
    type Elements = "Fire" | "Water" | "Air" | "Earth";
    type ExcludedElements = Exclude<Elements, "Fire" | "Air">;
    
    const element: ExcludedElements = "Water"; // Success! Excludes "Fire" and "Air"
    const forbiddenElement: ExcludedElements = "Fire"; // Error! "Fire" is excluded
```
`ExcludedElements`
: Our transformed type, derived from Elements using the Exclude mapped type. With the power of Exclude, we&rsquo;ve excluded the elements &ldquo;Fire&rdquo; and &ldquo;Air&rdquo; from our new type, allowing only &ldquo;Water&rdquo; and &ldquo;Earth&rdquo; to remain.


<a id="org7357742"></a>

### Transforming Property Types: A Symphony of Type Transformations


<a id="orgbfe1aee"></a>

#### Modifying Property Types: Pick and Record types

Picture a symphony where notes dance and melodies intertwine. In the world of TypeScript, we have the harmonious `Pick` and `Record` types. They pluck or transform property types. Let&rsquo;s explore their utility:

`Pick<T, K>`
: computes a new type selecting specific properties `K` from the original type `T`.

`Record<K, T>`
: computes a new type by mapping each property key `K` to a corresponding property type `T`.

Let&rsquo;s review some examples of `Pick` and `Record`:
```typescript
    interface Song {
      title: string;
      artist: string;
      duration: number;
    }
    
    type SongTitle = Pick<Song, "title">;
    type SongDetails = Record<"title" | "artist", string>;
    
    // Only "title" property is allowed
    const songTitle: SongTitle = { title: "In the End" };
    // Only "title" and "artist" properties are allowed
    const songDetails: SongDetails = { title: "Bohemian Rhapsody", artist: "Queen" };
```
SongTitle
: Our transformed type derived from `Song` using the `Pick` mapped type. It selects only the `title` property, allowing us to focus on the song title.

SongDetails
: Our transformed type derived from the key `"title" | "artist"` and the type `string` using the `Record` mapped type. It maps each property key to the type `string`, creating a type that captures the song `title` and `artist`.


<a id="org9c2c68a"></a>

#### Replacing Property Types: Mapped Types with conditional types

Now, we explore how mapped types can work with conditional types to transform property types:

Conditional Types
: A type-level capability to adapt based on conditions. I wrote about [conditional types](https://www.susanpotter.net/software/using-conditional-types-in-typescript/) in a previous post, illustrating good and bad examples.

Let&rsquo;s observe conditional transformations with code examples:

```typescript
    type Pet = "Cat" | "Dog" | "Bird";
    type Treats<T extends Pet> = T extends "Cat" ? string[] : T extends "Dog" ? number : boolean;
    
    const catTreats: Treats<"Cat"> = ["Salmon Treats", "Catnip"]; // Array of strings
    const dogTreats: Treats<"Dog"> = 5; // Number
    const birdTreats: Treats<"Bird"> = true; // Boolean
```
`Treats<T>`
: Our transformed type, where the property type varies based on the condition in the conditional type. The resulting type adapts to the pet&rsquo;s type from the input, offering an array of strings for a cat, a number for a dog, and a boolean for a bird.

Above we observed the powers of Pick, Record, and the fusion of mapped types with conditional types. Using these TypeScript capabilities, we can reduce boilerplate and express the problem domain more directly.

So, grab your wands, summon your creativity, and let the transformation of properties and property types begin! But be judicuous.


<a id="org7c21d12"></a>

## Tips, Tricks, and Best Practices: Unleashing the Magic Safely


<a id="org85ed905"></a>

### Tips for Working with Mapped Types: Navigating the Magical Realm

As we traverse mapped types, it&rsquo;s essential to keep a few tips and tricks up our sleeves.

- **Consider performance implications:**
  -   Mind the size and complexity of your types.
  -   Large mappings can lead to longer compile times and increased memory usage.
  -   Strike a balance between expressiveness and performance for optimal code execution.

- **Beware of limitations and pitfalls:**
  -   Mapped types cannot create new properties that don&rsquo;t exist in the original type.
  -   Complex mappings or recursive transformations may result in convoluted and hard-to-read code.
  -   Stay vigilant and explore alternative strategies when faced with these challenges.

- **Master complex mappings and type inference challenges:**
  -   Embrace utility types like `keyof` and conditional types.
  -   Harness their power to navigate intricate mappings and overcome type inference hurdles.
  -   Experiment, iterate, and tap into the [vast resources of the TypeScript documentation](https://www.typescriptlang.org/docs/handbook/2/mapped-types.html) and developer communities.

With these suggestions, you can leverage TypeScript&rsquo;s mapped types while avoiding common pitfalls.


<a id="orgedc7685"></a>

### Best Practices and Guidelines: Taming the Magic for Large-Scale Projects

To tame the magic of mapped types in large projects, follow these best practices:

-   **Organize and maintain mapped types:**
    -   Group related types together.
    -   Create dedicated type files.
    -   Provide clear documentation.

This fosters maintainability and enables effective use by fellow wizards.

-   **Ensure type safety and compatibility:**
    -   Use type guards and strict null checks.
    -   Perform thorough testing.

Validate the safety and compatibility of your mapped types. Integrate comprehensive yet meaningful tests to check their usage is as expected.

-   **Leverage mapped types for clarity and maintainability:**
    -   Create reusable abstractions.
    -   Enforce consistent patterns.
    -   Reduce duplication.
    -   Avoid reinventing mapped types already provided by TypeScript.

Harness mapped types to enhance code readability and simplify maintenance tasks.


<a id="orgb294c29"></a>

## Wrapping it up

Throughout this article we embarked on a wild ride through the enchanted land of mapped types in TypeScript. We&rsquo;ve seen their transformative abilities, from modifying property modifiers to reshaping property types. We&rsquo;ve explored tips, tricks, and better practices for mapped types.

Unleash your imagination and continue exploring the enormous possibilities that mapped types offer. Yet avoid reinventing constructs as TypeScript already defines utility types for common uses.

Wield the magic of mapped types with care, always striving for clarity, maintainability, and type safety but do not overuse. Your future teammates will thank you later.