Understanding TypeScript Generics

Generics are a powerful feature in TypeScript that allow you to create reusable and type-safe components. By using generics, you can write functions. Whether you’re building a simple utility function or a complex data structure, understanding and leveraging generics can greatly enhance the flexibility and reusability of your TypeScript code. Experiment with generics in your projects and see how they can help you write cleaner, more efficient code.

What are Generics?

Generics are a way to create components that work with a variety of data types instead of a single one. This means you can define a component (like a function or a class) without specifying the exact type it will use until it is actually needed. This leads to greater flexibility and reusability in your code.

For example, consider a function that takes an array of numbers and returns the first element:

function getFirstElement(arr: number[]): number {
    return arr[0];
}

This function works well for arrays of numbers, but what if we need a similar function for arrays of strings, booleans, or other types? Instead of writing separate functions for each type, we can use generics to create a single function that works with any type:

function getFirstElement<T>(arr: T[]): T {
    return arr[0];
}

Here, T is a type parameter that acts as a placeholder for the actual type we will pass to the function when we call it. This makes the getFirstElement function versatile and reusable for different types of arrays.


Using Generics with Functions

Generics can be particularly useful for functions that need to handle different types of data while maintaining type safety. Let’s see some examples:

Example: 1

A simple example of a generic function is the identity function, which returns whatever value it receives:

function identity<T>(value: T): T {
    return value;
}

When you call this function, TypeScript will infer the type based on the argument passed:

let number = identity(42); // number is of type number
let text = identity('Hello'); // text is of type string

Example: 2


Utilizing generics, the following function accepts an existing object as the first parameter, adds a new key-value pair to it, and returns the updated object.

function addKeyValue<T, K extends string, V>(obj: T, key: K, value: V): T & Record<K, V> {
    return { ...obj, [key]: value } as T & Record<K, V>;
}

let person = { name: 'Alice' };
let updatedPerson = addKeyValue(person, 'age', 30); // { name: 'Alice', age: 30 }

let book = { title: '1984' };
let updatedBook = addKeyValue(book, 'author', 'George Orwell'); // { title: '1984', author: 'George Orwell' }

console.log(updatedPerson); // { name: 'Alice', age: 30 }
console.log(updatedBook); // { title: '1984', author: 'George Orwell' }

In this function:

  • T represents the type of the existing object.
  • K extends string, represents the type of the key, constrained to be a string.
    • In TypeScript, the extends keyword is used to apply constraints to generic types. When you see K extends string, it means that the type parameter K is constrained to types that are assignable to string. In other words, K must be a string type.
  • V represents the type of the value.

The function returns a new object that includes all properties of the original object along with the new key-value pair. The return type T & Record<K, V> signifies that the resulting object has all properties of T and the new property of type V associated with the key K.


Feel free to reach out if you have any questions or need further clarification on TypeScript generics!

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top