How to better understand TS generics? the basics

Matan Cohen
Wix Engineering
Published in
7 min readNov 3, 2022
Photo by Cookie the Pom on Unsplash

This is the first blog post in a series of articles. In this series I will try to help you understand all the one letter words and triangle brackets in your codebase.

In many languages such as C# and Java, the concept of Generics has existed for a long time.
Generics are tools used to create reusable code; they allow us to create code that can work with multiple types.

What are Generics in typescript?

Generics in typescript derive from the same idea as other languages.

They are a way to create types from other types, and they allow us to parameterize types.
Enough with the big words, let’s understand why Generics are useful tools from a concrete example.

Let's create a function that returns the first element of an array.

The first use case is using a number array:

Some days have passed and we are pretty happy with our new head function, but now we need to get the first element from a string array.

Ok that’s not a problem, let’s add support for strings.

First, we try to use a union type.

We can feel that something is wrong here.

Photo by 傅甬 华 on Unsplash

When using numbers array as the input to head, we know that there is no way that we can get back a string.

We can try using any:

But we get no type information for firstNum , we know it can only be a number or undefined but with using any we lose this information.

Typescript Generics to the rescue!

head is a reusable function, the only thing that it needs to know is that it is given an array.

And the return type will be the type of element that is in the array, or undefined.

To rewrite head types let’s understand what Type parameters are.

Type parameters are placeholders for a specific type.

  • <T> The triangle brackets to the right of liftArray is where we defined the ‘type parameters’. We can define one or more, separated with a comma <T,E>
  • t:T Is where we specify the type of t, which is our function input, we specify the type parameter T
  • :T[] We state the return value is an array of type parameter T

Let’s see when T gets its specific value:

In this case T is getting the number type as we call liftArray<number>. At this point, we create an instance of the generic type.

Usually, you don’t see the caller of the function specify the type as we did.

Instead, you see liftArray(5), because Typescript can infer the type from the function input.

Back to our example of creating head.

We add a type parameter to head function to make it generic.

We added T as a type parameter to head<T>, then we used this parameter for the input arr: T[] which indicates that it’s an array of type T.

And we stated the return type T | undefined .

From lines 5 to 8 we can see the magic of generics in action.

Photo by Matt Palmer on Unsplash

We can send any type of array to head and we will get back an element that is the same type as an array element.

Using numArr of type number[] typescript infer that T = number and we get back number or undefined .

Works the same for string using strArr .

Generic allowed us to create highly reusable and type safe code.

Challenge - build a generic map function:

Build a generic function that receives an array and a mapping function.

  • Make the code compile.
  • map return value type should be the same as the array element.
  • Don’t use Array.prototype.map() .

Click on the link below to start hacking:

Check your solution here:

Generics type parameters are useful but as in many other fields in programming, adding constraints can make something better.

Photo by Karine Avetisyan on Unsplash

Generic Constraints

Generics constraints are a way to restrict our type parameters and reduce the possible types that they can be.

To explain why we need to restrict our types I’ll use an example:

We are running an e-commerce site for a pet store.

Photo by Peter Plashkin on Unsplash

We want to create a function that returns the total amount to pay at the checkout.

We have many types of items in our store and each one of them is represented using an object.

For example, dog food is represented by:

type DogFood = {
mainIngredient: 'salmon' | 'beef' | 'chicken';
size: 'big' | 'small' | 'medium';
price: number;
};

Each one of these objects has many different properties but they all have price:number property.

First, let’s try to use generics with no constraints

Unfortunately, this code will not compile,

The compiler error:

Property 'price' does not exist on type 'T'

Typescript does not have any way to know that cartItem has the property price on it.

With no constraints on T , it basically functions as any inside calcCartTotal .

It’s important to notice that in the head function example we have not added constrained to the type parameter but we used it for the input and the output.

In that case, the type parameters helped us to create a “relationship” between the type of input and the output of the function.

Use type parameter at least twice or add a constraint to them.

We want our calcCartTotal to be able to get cart with many different types of items and OfCourse to be type safe.

We will add constraints to the type parameter,

First thing first: syntax. using the example from the official docs:

To apply constraints to the type parameters we use the keyword extends.

The extends keyword here means the type parameter can be any type that at least fulfills the requirement that comes after the extended keyword.

In this case, the type parameter can be any object that has property length of type number.

The Type before ‘extends’ keyword is more specific than the type after it.

A way to remember this is that in object-oriented programming, a class that extends another class is more specific.

Back to our pet store example (the customers are getting upset!),

These are the types for our different cart items:

We created the interface cartItem which has a single property price:number .

All the other interface extends it.

Interface extends means that the interface will get all the properties from the interface it extends.

So, Leash will have price from CartItem and size and color from its own declaration.

We can stop here for a second and try to implement calcCartTotal using a union instead

This code compiles and works perfectly, but there is a small problem, next week we will get a new delivery of Catnip.

Let’s try to add it to our cart:

This code breaks!

Type 'Catnip' is not assignable to type 'Leash | SqueakyToy'.
Type 'Catnip' is missing the following properties from type 'SqueakyToy': shape, color

As we did not specify it in cart type in order for it to compile, we have to add it to the union like so:

However, we don’t want to update the type of cart every time we have a new item in the store. That’s a big problem!

Luckily adding constraints to our type parameters will solve this problem.

Let’s create a generic constrained version of calcCartTotal

T extends CartItem which means that the type parameter T has to be an object type with price property of type number. Of course, it can have many more properties.

Let’s see the usage of our new function

In line 33 we see no compile errors as leash and squeakyToy has the property price of type number which satisfied the constrained we put on T .

We created a function that can accept an array of any CartItem, even items that do not exist in the store yet. Meaning we will not have to change cart type any more.
Also, we made our friends on all four very happy.

Photo by Honest Paws on Unsplash

Sum up

In this article we explored what Type Script Generics are.

We learned how to use Generics and the power of adding constraints.

I hope that by using these tools you will be able to write a more abstract and reusable code and of course understand others code better.

There is a lot more to learn about TS generics so stay tuned, in my next article we will learn about Generic Scope and Conditional Types.

In this article I have mainly used the Typescript Docs

And the wonderful Frontend Masters course

Keep on learning!

--

--

Matan Cohen
Wix Engineering

Frontend Tech lead at Wix, on the path of mastering functional programming.