What are Generics in Typescript?

What are Generics in Typescript?

Introduction

Over the course of the last few years, Typescript has emerged as the de-facto standard for writing type-safe Javascript code. This added type-safety gives developers confidence in their code and helps them catch errors even before the code is executed.

The best part of Typescript is

Every JavaScript program is also a TypeScript program

What this means is that you can start writing Typescript code using just the basic features of the language. You don't need to use all the advanced features right from Day 1. You can keep learning more advanced features and use them in your codebase on the go.

One such advanced and super powerful feature of Typescript is Generics

What are generics?

Generics are like any other variable that you define and store value in. However, generics are special because the value that can be stored in these variables is the type information. For example - number, string etc.

Confused? Let's try to understand it with a simple example.

In the below code snippet, we have defined an array with a type of number[]. Sound familiar right?

const arr: number[] = [1,2,3,4];

Another way to write these using built-in generics is below

const arr: Array<number> = [1,2,3,4];

Here, we passed the type number as a variable to Array. We can actually pass any type like this. For example

const strArr: Array<string> = ['a', 'b', 'c'];
const boolArr: Array<boolean> = [true, false, true];

The above are the simplest examples of using generics in typescript.

What's the major use case for generics?

Let's try to understand this with the help of an example. Suppose we need to write a function that takes a list of numbers and returns a comma-separated string of those numbers.

Input: [1,2,3,4,5]

Output: "1,2,3,4,5"

We can write a simple function in typescript for this as below

function getListAsString(arr: number[]) {
    // Null check
    if (!arr || arr.length === 0) {
        return '';
    }

    // Add first element to string
    let str = `${arr[0]}`;

    // Add remamining elements with comma
    for(let i = 1; i < arr.length; i ++){
        str = `${str},${arr[i]}`;
    }
    return str;
}

console.log(getListAsString([1,2,3,4,5]));

This works perfectly for the argument of type number[]. Now let's say you want to reuse the same function for string arrays as well.

Input: ['apple','bat','cat']

Output: "apple,bat,cat"

However, since the input type of arr is defined as number[] currently, the typescript compiler will throw an error.

Well, one option is to modify the type to be (number | string)[]. Another option is to use the good old friend any[] here. However, with both the options, you would lose out on the type checking benefits. The function would start accepting arrays of mixed types as well. For example, getListAsString(['apple','bat','cat', 5])) would also not throw an error.

This is exactly the use case of generics. We can pass the type information while calling the function getListAsString.

Using generics in the function

We can declare a generic T (for type) in the function below

function getListAsString<T>(arr: T[]) {
    // Null check
    if (!arr || arr.length === 0) {
        return '';
    }

    // Add first element to string
    let str = `${arr[0]}`;

    // Add remamining elements with comma
    for(let i = 1; i < arr.length; i ++){
        str = `${str},${arr[i]}`;
    }

    return str;
}

Here we are telling typescript that T will have a type and the argument provided will be an array of that type. If the value of T is number, the argument will be of type number[]. If the value of T is string, the argument will be of type string[].

We can pass the value of generic type T using <> while calling the function.

For example, here we have passed the type T as number

getListAsString<number>([1,2,3,4,5])

Similarly, we can pass string as below

getListAsString<string>(['apple','bat','cat'])

Can we pass multiple generics to a function?

Why not? You can pass any number of generics to a function.

For example, let's say we want to create a function that merges two arrays of different types.

Input: ['a','b','c'], [1,2,3]

Output: ['a','b','c', 1,2,3]

We can do something like below. Try to go through it slowly to understand whats happening here.

function mergeTwoArrays<T, U>(arr1: Array<T>, arr2: Array<U>) {
    return [...arr1, ...arr2];
}

console.log(mergeTwoArrays<string, number>(['a','b','c'], [1,2,3]))

So here while calling the function mergeTwoArrays, we are passing two generics i.e. string and number. These get assigned to T and U respectively. So finally the type of arr1 is becomes Array<string> and that of arr2 becomes Array<number>.

Defining type constraints

Another powerful application of generics is to define constraints on the types using generics.

For example, you want to create a function that returns the length of something. This something could be an array or a string or any other data type which has a .length defined.

We can put such a constraint as below

interface TypesWithLength {
    length: number;
}

function getLength<T extends TypesWithLength>(val: T) {
    return val.length;
}

getLength<number[]>([1,2,3]);
getLength<string>("Hello");
getLength<number>(1); // Type 'number' does not satisfy the constraint 'TypesWithLength'

Here on the first line, we defined an interface TypesWithLength with a single property length of type number. So any value which adheres to this interface needs to have a length property of type number.

Further, we extended the type T and put this constraint on that type using T extends TypesWithLength. Now, this function will accept only arguments which adhere to the interface TypesWithLength. That's why the last line where we passed the type as number throws the error.

Conclusion

Generics is one of the most powerful features of typescript. While this was just an introduction to generics where we covered a few of the use cases where generics could make our life easy, I would strongly urge you to read more about it and start using them in your codebase.

Thank you for reading, hope you found this useful. Let me know in the comments below.

Happy coding!