Generics in TypeScript: why they are needed and how to use them for beginners

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 are they important?

\n\n

When learning TypeScript, you quickly encounter a situation where you need to write a function or class that can work with different data types. Without special tools, you would have to either duplicate code for each type or use any, which kills all the magic of strict typing. This is where Generics come to the rescue.

\n\n

Generics are a powerful TypeScript mechanism that allows you to create components (functions, classes, interfaces) that work with different types while maintaining strict typing. Imagine you are writing a universal container that can store apples, books, and numbers, but TypeScript knows exactly what is inside in each specific case.

\n\n

In this article, we will break down why Generics are needed, how to declare them, and how to use them in real projects. You will learn how to make your code more flexible, safe, and reusable.

\n\n

Generics Basics: syntax and your first function

\n\n

Let's start with a simple example. Suppose we need a function that takes an argument and returns it (identity function). Without generics, we would write it like this:

\n\n
function identity(arg: number): number {\n    return arg;\n}
\n\n

But this function only works with numbers. To make it universal, we can use any:

\n\n
function identity(arg: any): any {\n    return arg;\n}
\n\n

The problem is that we lose type information. TypeScript doesn't know that the same type that was passed will be returned. This is where Generics come in:

\n\n
function identity<T>(arg: T): T {\n    return arg;\n}\n\n// Usage:\nlet output1 = identity<string>("Hello"); // type string\nlet output2 = identity<number>(42);       // type number
\n\n

Let's break down the syntax:

\n
    \n
  • <T> — this is a type parameter. You can use any letter, but by convention, T (Type), U, V are most often used.
  • \n
  • arg: T — the argument has type T.
  • \n
  • : T after the parentheses — the function returns a value of type T.
  • \n
\n\n

Now TypeScript knows: if you passed a string, the output will be a string. If a number — a number. Type inference often works automatically, so you can just write identity("Hello") — TypeScript will understand that T is string.

\n\n

Generics in functions: working with arrays and constraints

\n\n

Most often, generics are used for working with arrays or collections. Consider a function that returns the first element of an array:

\n\n
function getFirst<T>(arr: T[]): T | undefined {\n    return arr[0];\n}\n\nconst numbers = [1, 2, 3];\nconst firstNumber = getFirst(numbers); // type number\n\nconst strings = ["a", "b", "c"];\nconst firstString = getFirst(strings); // type string
\n\n

Note: T[] is shorthand for Array<T>. The function works with any array, but returns an element of exactly the type stored in the array.

\n\n

Constraints using extends

\n\n

Sometimes you need the type T to have certain properties. For example, we want to get the length of an object. Not all types have a length property, so we can impose a constraint:

\n\n
interface HasLength {\n    length: number;\n}\n\nfunction getLength<T extends HasLength>(arg: T): number {\n    return arg.length;\n}\n\ngetLength("string");     // OK, string has length\ngetLength([1, 2, 3]);    // OK, array has length\ngetLength(42);           // Error! Number does not have length

Blogs

Book Recommendations