TypeScript Map Type – Tutorial With Examples

By Sruthy

By Sruthy

Sruthy, with her 10+ years of experience, is a dynamic professional who seamlessly blends her creative soul with technical prowess. With a Technical Degree in Graphics Design and Communications and a Bachelor’s Degree in Electronics and Communication, she brings a unique combination of artistic flair…

Learn about our editorial policies.
Updated November 23, 2024

This tutorial explains what TypeScript Map Type, how to create and use it using programming examples:

In this tutorial, you will learn about the TypeScript Map types. This may be an advanced topic, but believe me, it is a very important topic as far as the TypeScript world is concerned. You will learn how to create and implement TypeScript Map type.

Concepts that help us avoid repetition, help us write clean and a few lines of code are worth learning in the development industry.

=> Check ALL TypeScript Tutorials Here

A mapped type allows us to create a new type by iterating over a list of properties of existing types thereby avoiding repetition and as a result, we end up with a cleaner, shortcode as mentioned earlier.

TypeScript Map Type

TypeScript Map Type

A Simple Example

For example, if we have a list of properties in a union type as shown below

‘propA’ | ‘propB’ | ‘propC’

We can use the list to create a new type where each of these properties will correspond to some value. To help us understand more in regards to TypeScript Map types, let us proceed and look at some practical examples. You can learn more here.

Creating a New Type from an Existing One Using keyof Keyword

Open your IDE of choice and I will personally use the vs code for this tutorial. Let us start with a very simple example. Let’s say we have a list of properties PropA and PropB.

We can now use this list to create a new type as shown in the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType = {

}

Inside MyMappedType type, let us iterate over our Properties by typing the following inside a square bracket, we say that for every property P this type variable will hold the property name.

This means that for every property P in the list of Properties, we will create a new property of MyMappedType, which we will call our new property Properties as mentioned previously.

We can proceed and assign some value to this property. For example, we can describe each of these properties as a Boolean. As a result, we will get a new type where each of the properties will belong to the Boolean type.

We can also use the property name on the right side of our expression as shown in the code snippet below

type Properties = 'propA' | 'propB';
type MyMappedType = {
  [P in Properties]: P;
}

We will get a new type where each property pool will have its name as a value. Later, we will use this property name on the right side of the expression to get the type of the property value from some existing type.

We can use a mapped type to create a new type from an existing type. We will use generics to accomplish this. Let us turn our mapped type into a generic type. Thus, let us use the properties list as a generic type parameter.

We will call this parameter Properties as shown in the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType<Properties> = {
  [P in Properties]: P;
}
Properties Parameter

Oops! we get an error as shown in the image above. Let us check it out, Oh! Properties are not assignable to type string, number, or symbol.

TypeScript expects a property to be either a string, number, or a symbol as shown by the help of the intelligence image below, but the type parameter properties that can get in our property at this moment can be anything from a Boolean to a mapped!

type parameter

To fix this error, let us add a generic type constraint to make sure that every property in this union is either a string and number or a symbol.

So now, we can create a new type from this generic. We can pass the property list as a generic type parameter and we will get a new type.

We can then proceed and use a mapped type to create a new type from an existing type. To do this we will have to modify our generic, so instead of taking the properties as the generic type parameter, we will take the whole type. Let us call this Type T and proceed to copy this type.

To do this, we will need to get a list of properties of our type i.e., MyMappedType, and iterate over this list to create a new type with those properties.

As shown in the code snippet below, to get the properties of our type as a union, we can use the keyof keyword i.e. for every property P in keyof T and keyof T gives us a union of all the properties in T.

type Properties = 'propA' | 'propB';
type MyMappedType<T> = {
  [P in keyof T]: P;
};

type MyNewType = MyMappedType<'propA' | 'propB'>;

Basically, we will copy the type T and on the right side, we can use the property name P to get the type of the value in T. For this, we say T square brackets b thus we get the type of the value of P in T.

What happens is that this type will just copy that type T without modifications. As evident in the code snippet below, we pass some type with property a is a and b is b.

type Properties = 'propA' | 'propB';
type MyMappedType<T> = {
  [P in keyof T]: T[P];
};

type MyNewType = MyMappedType<{ a: 'a'; b: 'b' }>;

As a result, we get a new type with the same properties and values as shown in the below image.

MyNewType

Mutability and Optionality

Now, instead of just copying this type, let us try to modify it somehow, for example, we can make each property readonly as shown in the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType<T> = {
  readonly[P in keyof T]: T[P];
};

type MyNewType = MyMappedType<{ a: 'a'; b: 'b' }>;

We will get a new type with all the properties as readonly as shown in the image below

read only

or we can make each property optional by using a question mark as shown in the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType<T> = {
  [P in keyof T]?: T[P];
};

type MyNewType = MyMappedType<{ a: 'a'; b: 'b' }>;

We will get the new type with optional properties as shown in the image below,

New Type with optional properties

or we can modify the type value somehow. For example, make it nullable and we will get a nullable type as shown on the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType<T> = {
  [P in keyof T]: T[P] | null;
};

type MyNewType = MyMappedType<{ a: 'a'; b: 'b' }>;

Thus, every property can be null as shown in the image below too.

Null property

Recreation of the Pick Type

TypeScript’s built-in types like pick and record use TypeScript Map types behind the scenes.

In our next example, let us have a look at how to recreate these types using TypeScript Map types. Let us start with a pick, I will call it Pick1 because Pick is a reserved word in TypeScript. Pick takes an existing type, picks some properties from this type, and creates a new type with the same properties that it picked.

We will tell it which properties to Pick. Let us proceed and take two parameters at the generic type parameters. The first one is the existing type, and the second one is the list of properties that we would like to pick from type T.

Let us call this type parameter Properties, and we need to make sure that these properties exist in type T. To achieve this, we will add a generic type constraint, saying that properties belong to the list of properties of type T, and to get the list of properties of type T, we use the keyof keywords and keyof T as shown in the code snippet below.

type Pick1<T, Properties extends keyof T> = {};

Now let us iterate over the properties that we would like to pick for this P type, for every property in Properties we create this property with the original type of this property value.

This means, that we take this as T[P]. Now we can use this type to pick a few properties from an existing Type, for example, we will take only property a from types a and b as shown in the code snippet below.

type Properties = 'propA' | 'propB';
type MyMappedType&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt; = {
  [P in keyof T]: T[P] | null;
};

type MyNewType = MyMappedType&amp;amp;amp;amp;lt;{ a: 'a'; b: 'b' }&amp;amp;amp;amp;gt;;

type Pick1&amp;amp;amp;amp;lt;T, Properties extends keyof T&amp;amp;amp;amp;gt; = {
  [P in Properties]: T[P];
};

type MyNewType2 = Pick1&amp;amp;amp;amp;lt;{a: 'a', b: 'b'}, 'a'&amp;amp;amp;amp;gt;;

As a result, we get the new type with only the property a from the original type as shown on the intellisence image below.

intellisence image

We can also take two or more properties using a union as demonstrated in the code snippet below.

type MyNewType2 = Pick1&amp;amp;amp;amp;lt;{a: 'a', b: 'b'}, 'a' | 'b'&amp;amp;amp;amp;gt;;

We will get the same object as shown in the image below because it has only two properties.

Two Properties

How to Use TypeScript Map Type in Record Type

The other type that I would like us to recreate is the Record. First, let us check the original type definition of the Record.

To achieve this, let us put the cursor over the Record type name and press the F12 key so as to get the peek definition.

The intellisence result is shown in the image below.

Record Type

As clearly shown on the image above, Record is a generic type that takes two type parameters K and T. The first type parameter describes the Record’s keys and the second type parameter T describes the Record’s values.

Then, for every key in K, the Record allows us to create the property [P in K] of the type T. An interesting notation is keyof type any. Let us proceed and check what it resolves by hovering over the key parameter.

key parameter

As evident from the image above, K extends a union of string, number, and symbol. Thus, keyof any resolves to this union type.

Next, let us have a look at how to use the record type. Let us proceed and copy the definition to have it for reference.

We will then just paste it and rename it as Record1 as shown below.

type Record1&amp;amp;amp;amp;lt;K extends keyof any, T&amp;amp;amp;amp;gt; = {
  [P in K]: T;
};

Let us proceed and use our Record1, which will be a record of strings for the keys and numbers for the values as shown in the code snippet below.

const someRecord: Record1&amp;amp;amp;amp;lt;string, number&amp;amp;amp;amp;gt; = {}.

Next, we proceed and use our Record1, which will be a record of strings for the keys and numbers for the values.

We can go ahead and add properties to some records on the fly like, let’s say we have 10 apples. We can also say that we have 10 oranges, and we can continue adding properties to this record.

Variation between a Record Type and an Index Signature Interface

Now you might ask, why do I use a record if I can use an index signature? Let us create another signature and we are going to call it Record2. The keys in this index will have strings and numbers for the values as depicted in the code snippet below. Just exactly the same as we have with the record type we created previously.

This indexing initiative will be the same as the Record1 type, we can even replace it with Record2.

So, the big question you might be asking yourself now is, why do we need a record if we can use an index signature? The issue posed is that the index signature has a limitation as to what keys we can describe on its body or rather block.

For example, we cannot use a union to describe the keys of an index signature. For instance, we cannot say string or number as shown in the code snippet below.

interface Record2 {
  [key: string | number]: number;
}

As evident in the image below, we will get an error in the signature parameter type saying that the parameter key must be a string, number, symbol, or a template literal.

error in the signature parameter

Thus, we cannot use a union to describe the keys of index signatures as shown in the above code snippet without having an error.

We can also use either string as shown below

interface Record2 {
  [key: string]: number;
}

or numbers as shown below

interface Record2 {
  [key: number]: number;
}

While using the records, we can say that these record keys may be of type string or number, or maybe some union of string literals. Let us have Record1 and the keys can be numbers or strings and the values we leave as a number as shown in the code below.

type Properties = 'propA' | 'propB';
type MyMappedType&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt; = {
  [P in keyof T]: T[P] | null;
};

type MyNewType = MyMappedType&amp;amp;amp;amp;lt;{ a: 'a'; b: 'b' }&amp;amp;amp;amp;gt;;

type Pick1&amp;amp;amp;amp;lt;T, Properties extends keyof T&amp;amp;amp;amp;gt; = {
  [P in Properties]: T[P];
};

type MyNewType2 = Pick1&amp;amp;amp;amp;lt;{a: 'a', b: 'b'}, 'a' | 'b'&amp;amp;amp;amp;gt;;

type Record1&amp;amp;amp;amp;lt;K extends keyof any, T&amp;amp;amp;amp;gt; = {
  [P in K]: T;
};

const someRecord: Record1&amp;amp;amp;amp;lt;number | string, number&amp;amp;amp;amp;gt; = {};
someRecord.apples = 10;
someRecord.oranges = 10;

interface Record2 {
  [key: number]: number;
}

We can now add a number as a key to this record. Let us say one is equal to one.

someRecord[1] = 1;

Also, I can describe the keys as a union of strings literal that these records will have Keys A and B, which are numbers.

const someRecord: Record1&amp;amp;amp;amp;lt;'A' | 'B', number&amp;amp;amp;amp;gt; = {};

Now we have to initialize A as 1 and B as 2, as shown in the code snippet below and that’s it about records.

const someRecord: Record1&amp;amp;amp;amp;lt;'A' | 'B', number&amp;amp;amp;amp;gt; = {A: 1, B: 2};

Adding Property to a Mapped Type

Suppose we want to add a specific property to a particular mapped type. For example, we want to add a property called someProperty to Record1.

The mapped type doesn’t allow me to do this, but I can still do it using an intersection as shown in the code below.

type Record1&amp;amp;amp;amp;lt;K extends keyof any, T&amp;amp;amp;amp;gt; = {
  [P in K]: T;
} &amp;amp;amp;amp;amp; { someProperty: string };

As a result, someProperty will now be of type string and some records should now have some property as evident in the image below.

someProperty

As you can observe in the below intellisence image, a mapped type i.e. Record1 is merged with another type that has someProperty.

someProperty Type

Since someRecord is Record1, we will have to add someProperty to it as demonstrated in the code snippet below.

const someRecord: Record1&amp;amp;amp;amp;lt;'A' | 'B', number&amp;amp;amp;amp;gt; = {
  A: 1,
  B: 2,
  someProperty: 'abc',
};

Below is the complete code for this tutorial.

type Properties = 'propA' | 'propB';
type MyMappedType&amp;amp;amp;amp;lt;T&amp;amp;amp;amp;gt; = {
  [P in keyof T]: T[P] | null;
};

type MyNewType = MyMappedType&amp;amp;amp;amp;lt;{ a: 'a'; b: 'b' }&amp;amp;amp;amp;gt;;

type Pick1&amp;amp;amp;amp;lt;T, Properties extends keyof T&amp;amp;amp;amp;gt; = {
  [P in Properties]: T[P];
};

type MyNewType2 = Pick1&amp;amp;amp;amp;lt;{a: 'a', b: 'b'}, 'a' | 'b'&amp;amp;amp;amp;gt;;

type Record1&amp;amp;amp;amp;lt;K extends keyof any, T&amp;amp;amp;amp;gt; = {
  [P in K]: T;
} &amp;amp;amp;amp;amp; { someProperty: string };

const someRecord: Record1&amp;amp;amp;amp;lt;'A' | 'B', number&amp;amp;amp;amp;gt; = {
  A: 1,
  B: 2,
  someProperty: 'abc',
};
//someRecord.apples = 10;
//someRecord.oranges = 10;
someRecord[1] = 1;

interface Record2 {
  [key: number]: number;
}

Conclusion

In this tutorial, we learned how to create and use TypeScript Map type.

Sometimes we find ourselves in a situation where we need to use another type to create a new type, this is where a typed map comes in handy. It allows the creation of a new type from an existing type.

TypeScript Map types are based or rather built upon index signature syntax, which is majorly utilized when declaring property types that haven’t been declared previously.

TypeScript Mapped types are generic, created by using the key of a keyword and utilizing the PropertyKeys union. Randomly which affects mutability and ? which affects optionality are the two additional modifiers that are used during mapping.

In the TypeScript Map type, we can remap keys by using the “as” clause. We can also take advantage of the template literal type features to create new property names from the existing ones.

We can map over unions of string | number | symbol i.e. arbitrary unions, in addition, we can also map over any type of union too.

Typescript Map type is very powerful and marks my words, in the development world you can save lots of time, write clean code, a few lines of code, and avoid repetition when leveraging what we have learned in this tutorial.

PREV Tutorial | NEXT Tutorial

Was this helpful?

Thanks for your feedback!

Leave a Comment