This post is a simple introduction to generics in TypeScript.
What Is Generics?
Generics is a way of writing code that will work with a wide range of objects and primitives. It’s really useful when we want to write codes that will work with any sort of type, interface, or class definition.
For example, a function to find an element in a list where the elements can be strings, numbers, or any other type. Or maybe you have a Queue class that need to work with any type of primitive. These scenarios are when we need to use generics.
Let’s first look at a code snippet that uses generics.
Here, we have a function called
val of type
Note that the
T is only a convention. You can name it whatever you want.
Let’s see how it works when we call the function with arguments of different type.
And the output:
As you can see, the function works with every type. All we have to do is specify a type inside the angle brackets, then all
T inside the function will be replaced with that type.
print<number>() for example. All
T that appear inside the function will be replaced with
And if we give an argument that’s not a number, we’ll get an error.
For example, if we run
print<number>("hello"). We’ll get the following error.
Typescript prevents us from calling a generic function with the wrong type as an argument.
Note that we don’t need to explicitly specify the type the function takes. It can be inferred. For example the code below will still work as expected.
We are not limited to one generic in TypeScript. We can use as many as we needs.
Here, we have a function that take 2 generic types. Let’s see how it works using the code below.
Notice that the 2 generics can also be of the same type as shown at line 3.
You can see how powerful generics is from the examples above. Our generic function now works with any type. But usually, we want to only allow a specific set of types to be used within our generic. We can accomplish this by constraining the type using the
Now we are constraining the
T on the function above to either
Array<number>. This means that whatever
T is used within our code, it can only be interpreted as either an array of string or an array of number.
If we try to call the function with array of boolean,
takeArray([true, false]), we’ll get the following error.
Array<T> is a predefined type from the standard TypeScript type definitions.
We can also construct a generic type from another generic type.
Here, our function take two generic types,
K. The type
K is constrained to be the value computed from the
keyof operator on type
keyof operator will return a string literal type of the object’s properties. Therefore
K will be constrained to the property names of the type
Let’s look at an example.
The first and second line of the code will produce the following output as expected:
But the third line will result in an error because the property
age doesn’t exist on type
keyof T which is extended by type
Creating A New Object Within Generics
There might be a time when we need to create a factory function that return an instance of a class.
Surprisingly, the code above results in the following error.
This is happening because the name of
T only exists at compile time. We can use
T for type checking but not for constructing an object of type
T unless we have access to the constructor.
In order to fix the code above, we need to refer to type
T by its constructor function.
With this change, our code will now compile and work as expected.
That’s it for the introduction to generics in TypeScript 🎉.
Having a good understanding of generics is really important to use the more advanced TypeScript features like Mapped Types), Conditional Types, etc.