Mapped Types page
Learn how to create new types with mapped types!
Overview
Due to syntax similarity, let’s do a quick recap on using index signatures to define object types. Then we will dive into what mapped types are and how to define them, followed by practical applciations of mapped types. Finally, we will work through exercises building out some commonly used mapped types.
Index Signatures
Index signatures are a way to define object types when you know the general shape of any object but nothing more specific than the key type and the value type. Let’s imagine we have an object that looks something like this:
The general shape of this object is a key of type string
and a value of type number
. We could define a type with an index signature to match this general definition.
An index signature consists of two parts:
- The indexer –
[itemName: string]
- Defines the type the keys are allowed to be
- The property’s value type –
number
- Defines the type of the property
One thing to be aware of with defining types using index signature is the value may or may not be there, but TypeScript makes it seem as though it’s always present.
With interfaces and other type definitions, you can add a ?
to delineate the property is optional. This is not the case with types defined using index signatures. To achieve the optional type-safety with index signature type you can create a union with the value type and undefined
.
Requiring Certain Properties
Types defined using index signatures are great since they provide a lot of flexibility in what can be associated with the type but what if we wanted to require something to be there? In these situations, you can add those properties after the general key definition.
When defining required properties, the required properties cannot violate the index signature types. In the example above pokeball: number
conforms to [itemName: string]: number
; however, if we change pokeball to be of type string
TypeScript doesn’t allow it, since it’s a string
indexing another string
.
The error above can be remedied by expanding the type for the value with a union.
Multiple Indexers
It is possible to have more than one indexer and have different value types for indices. To understand these mechanics, we first have to talk about what can and can’t be the type inside an indexer. TypeScript does not allow us to index with anything; it only lets us index with three types string
, number
, and symbol
.
Tip: For objects to be keyed with a union or something besides these three try the
Record
utlity type!

These three types have a strange relationship
number
is a proper subset ofstring
symbol
is a proper subset ofstring
number
and symbol are mutually exclusive
In order for TypeScript to provide type safety, it has to be able to differentiate between the keys passed in. If the indexer’s values have the same types, it doesn’t matter.
In order to have more than one indexer with different types, the indexer’s types must be mutually exclusive (no overlap). This means the only way we can have more than one indexer with different value types is for one indexer to be of type number
and the other to be of type symbol
.
The idea of mutual exclusion extends to individual properties, in the type above we have a shape for all number
and all symbol
and if we tried to throw in all string we’d get errors. But, if we only define certain strings (like we did with required properties), TypeScript would be able to make the delineation and provide type safety.
JavaScript Makes an Appearance
TypeScript is a superset of JavaScript and has to conform to its rules. This can cause some unexpected behavior when indexing with a number
. An example of this is arrays. In JavaScript arrays are just objects indexed with number
s; however, in JavaScript, all object keys are coerced to string
s.
This quirk extends to index signature types in TypeScript.
Readonly Property Modifier
While the optional syntax (?
) isn’t supported on index signature types, the index signature syntax does allow for the readonly
modifier. The readonly
modifier marks a property as immutable on an object meaning it cannot be re-assigned once set. If we wanted to make our PokemonNameList
type above unchangeable we could do it by putting the readonly
modifier at the beginning of the declaration.
This extends to any required property added to the type as well.
Note: required properties have access to all the syntaxes you are familiar with when defining object types with type and interface.
Sticking with our PokemonNameList
example type, although it looks like an array and even uses the array syntax, it doesn’t have some of the more fundamental properties of an array, like .length
.
Often times in development we need to know the length of a list, but it is not something we want to allow developers to overwrite. To accomplish this we can tweak our definition to include a readonly
required length property.
Note: In JavaScript the
firstThreePokemon
variable does have a.length
property since it is anArray
. TypeScript however, is not aware that it is an array, instead it thinks it is aReadOnlyPokemonList
which is why generally speaking you should avoid defining your arrays using an index signature. Instead you should useArray<T>
or the shorthand[]
.
Mapped Types
Mapped types are another way to generate types in TypeScript. Mapped types are a way to iterate through each key of an object type to create new types for those keys. Mapped types are generic types that extend upon the index signature syntax. The best way to understand it is to see it in action. Let’s take a look at a utility type that uses mapped types – Partial<T>
.
Partial<T>
is a common utility type that maps over a type and makes all the properties in the type optional. Below is the definition of Partial<T>
.
If the phrase P in keyof T
seems to bear some resemblance to a JavaScript for-in
loop, that’s good! We’re essentially doing the same thing but with types. What Partial
does is iterate through each of the properties, make them optional, and assign them whatever types they had before. Getting rid of some of the additional TypeScript in the mapping and looking at a concrete example helps illustrate this.
If we look at PartialPokemon
and get rid of the generics we could re-write this as such.
Since we know what keyof Pokemon
evaluates too, let’s substitute that out.
P
serves as a variable for mapping and can be named anything, let’s name it something more semantically relevant.
If we iterate through the key names we get something that looks like this.
Evaluating the index accessed types then leaves us with our final type.
Property Modifiers
We saw it a little bit when looking at Partial
but mapped types give us the opportunity to change two things about the properties of the type we are creating – whether or not it’s optional and whether or not it’s immutable. Like types defined with an index signature, the properties of a mapped type can be made immutable by applying the readonly
modifier. Using the readonly
modifier in a mapped type is exactly how the Readonly
utility type in TypeScript works.
Additionally, we can remove a readonly
modifier in a mapped type. To accomplish this we need a small tweak in our mapped syntax. Instead of readonly
, we add -readonly
, essentially subtracting off the readonly
modifier.
In Partial
we saw how we can create an optional property by adding the ?
. We can remove optionality the same way we remove the readonly
modifier – with a -?
. This is best illustrated by the Required
utility type.
Note: you can also add
+readonly
and+?
to your types. They are default though so they are often omitted for brevity.
Remapped Keys
TypeScript allows us to map over more than just the keys of the object. We can do this using a syntax very similar to type assertion (as
). To illustrate this look at the following types.
We’d like to make a bag type with properties on it matching the name of the item property on each of the Items
and having that be a function to return the amount (so bag.berries
is a function with this shape () => number
). To do this we must do two things — constrain the generic and remap via as
.
Let’s Make our own!
So far we’ve seen some mapped types that TypeScript provides for us in its utility types, so let’s walk through making our own. In this case, let’s look at creating a Pokemon
class whose constructor takes the types of the Pokemon. So far in the examples leading up to this, the type property of a Pokemon has been defined as a union "Normal" | "Fire" | ...
, but Pokemon can have more than one type, in fact, they can have up to two, like, in the case of Charizard – Fire and Flying. To create our class we want a single pokemon type (either a PokemonType
or a [PokemonType]
) or tuple that provides these options.
We may at first think something like this might work.
However, this has a flaw: it allows us to have duplicate types such as ["Fire", "Fire"]
. In this case, we need a type that has the original pokemon type and the list of the rest of the pokemon types. To create this we can use a mapped type that uses Exclude
within its iteration.
What this gives us is a type that has either a single pokemon type or a tuple with a single pokemon type and the rest of the pokemon types with the original pokemon type excluded.
Now we can use them in our class. Like we said above, we want to be able to pass any of the following.
To do this we can use a generic constrained to our PokemonType
and an index accessed type to create our class definition.
As we’ve seen mapped types can do much of the heavy lifting when it comes to creating types from types. They power many utility types leveraged in applications. Moving forward, we will only see them more frequently, especially as we move into our next section – conditional types.
Exercises
Exercise 1
Below is a generic type called To<T,K>
that is currently set to any
. Update the type to change all of the properties on T
to whatever is passed into K
. Take the following ToNumber
type for example, it serves as an alias for To
where K
is number
.
Click to see the solution
Exercise 2
Exercise 2:
Let’s recreate the Pick
utility type. _Pick
should take two generics, some object T
and a string literal union that is
some subset of keys from T
as K
.
Hint for Exercise 2 (click to reveal)
You may need to update the definition ofK
to get this type to work properly