Generics in TypeScript: Why They Are Needed and How to Use Them in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: What Are Generics and Why They Are Important

TypeScript is a strictly typed language that adds a powerful type system to JavaScript. However, strict typing can sometimes hinder writing flexible and reusable code. This is where Generics come to the rescue.

Generics are a tool that allows you to create components (functions, classes, interfaces) that work with different data types without losing type control. Imagine you are writing a function that should work with numbers, strings, and arrays. Without generics, you would have to either use any (which defeats the purpose of typing) or create separate functions for each data type. Generics solve this problem elegantly and safely.

In this article, we will explore why generics are needed in TypeScript and, most importantly, how to use them in practice — from simple functions to complex classes and type constraints.



Main Part



1. Generics in Functions: From Theory to Practice

The simplest and most common use case for generics is functions that accept and return data of the same type. Let's look at a classic task: a function that returns the first element of an array.



Bad approach (without Generics):

// Example with any — we lose type informationfunction getFirst(arr: any[]): any {    return arr[0];}

const num = getFirst([1, 2, 3]); // type of num is any, we don't know it's a number


Correct approach (with Generics):

// Using a generic type Tfunction getFirst<T>(arr: T[]): T {    return arr[0];}

const num = getFirst([1, 2, 3]); // type of num is numberconst str = getFirst(['a', 'b', 'c']); // type of str is stringconst bool = getFirst([true, false]); // type of bool is boolean


How does this work? We declare a type parameter <T> (the name can be anything, but by convention T, U, V are used). TypeScript automatically infers the specific type based on the passed argument. This is called type inference.



You can also explicitly specify the type when calling the function:

const num = getFirst<number>([1, 2, 3]); // explicit type specification


Another useful example is a function that returns a random element from an array:

function getRandom<T>(items: T[]): T {    const randomIndex = Math.floor(Math.random() * items.length);    return items[randomIndex];}

const randomNumber = getRandom([10, 20, 30]); // type is numberconst randomName = getRandom(['Anna', 'Ivan', 'Maria']); // type is string


2. Generics in Interfaces and Types

Generics work great with interfaces as well. This allows you to create flexible data structures that can store values of different types.



Imagine we want to describe an API response that can contain data of any type:

interface ApiResponse<T> {    status: number;    message: string;    data: T;}

// Usage for different data typesconst userResponse: ApiResponse<{ id: number; name: string }> = { status: 200, message: 'Success', data: { id: 1, name: 'Alexey' }};

const listResponse: ApiResponse<string[]> = { status: 200, message: 'List received', data: ['apple', 'banana', 'orange']};


You can also use generics with type aliases (type):

type Result<T> = {    success: boolean;    value: T;    error?: string;};

const successResult: Result<number> = { success: true, value: 42 };const errorResult: Result<string> = { success: false, error: 'Loading error', value: '' };


Note: we can co

Blogs

Book Recommendations