Generics in TypeScript

By Hemanta Sundaray on 2023-04-17

In TypeScript, generics are like "placeholders" that allow you to write code without knowing the exact type of data you'll be working with, and then later fill in those placeholders with the appropriate type when you actually use the code.

Consider the example below:

function arrayLength(x: number[]) {
  return x.length
}

console.log(arrayLength([1, 2, 3])) // Output: 3

We have a function called arrayLength() that takes an array of numbers as a parameter and returns the array's length.

What if we want to pass an array of strings? We could write another function, of course, and type annotate the parameter x as an array of strings, like this:

function arrayLengthStrings(x: string[]) {
  return x.length
}

console.log(arrayLengthStrings(["hello", "world"])) // Output: 2

As you can see, the only difference between the arrayLength() function and the arrayLengthStrings() function is the type of the parameter x. They both do the same thing: find the length of the array. However, by creating a separate function for each type of array, we are duplicating code and making our codebase more complex.

This duplication of code becomes an issue as the number of variations of arrays increases. For example, if we need to find the length of arrays of other types like arrays of booleans, arrays of objects, etc., we would need to write separate functions for each type, resulting in redundant code and increased maintenance overhead.

This is where generics come in handy. They allow us to work with multiple types of data without duplicating code. We can define a generic type parameter that represents the type of the array elements, and use it throughout our function. Here's an example:

function arrayLength<T>(x: T[]) {
  return x.length
}

console.log(arrayLength([1, 2, 3])) // Output: 3
console.log(arrayLength(["hello", "world"])) // Output: 2

Generics Syntax

In the example above, T is a placeholder that represents the type of the array elements. We can choose any uppercase letter or a meaningful name as the generic type parameter, although T is commonly used to represent a generic type. The angle brackets < > after the function name indicate that T is a generic type parameter.

We can then use T throughout the function to represent the type of the array elements. In this case, we're using T to define the type of the parameter x, which is an array of T elements (x: T[]). The type of T will be inferred based on the actual argument passed to the function when it is called.

Now we can use the arrayLength() function with different types of arrays:

console.log(arrayLength([1, 2, 3])) // Output: 3
console.log(arrayLength(["hello", "world"])) // Output: 2

In the first example, T is inferred as number because we passed an array of numbers as the argument. In the second example, T is inferred as string because we passed an array of strings.

We now have a single function that can work with arrays of any type, whether it's an array of numbers or an array of strings. We no longer need to worry about what type of data the array contains when defining the function. The actual type is filled in when we invoke the function. This makes the code more reusable and avoids duplication.

Note that generics are not limited to just functions, they can also be used with classes and interfaces.

Join the Newsletter