Menu

TypeScript Generics: Generic Functions, Types and Classes

Generics let a function, interface, type or class work with many types while keeping them connected: what goes in decides what comes out. Learn generic functions, type argument inference, several type parameters, generic interfaces and classes, defaults, and when not to use generics.

This page includes runnable editors - edit, run, and see output instantly.

Generics are type parameters. A generic function declares a placeholder type, usually called T, and each call fills it in, so the types that go in decide the types that come out. One function then works for numbers, strings and your own objects, and every call is still fully typed.

<T> after the function name declares the type parameter. items: T[] uses it for the input and T | undefined for the output, which ties the two together. You never wrote number or string: TypeScript inferred T from the argument.

Why Not any or unknown?

Without generics you would type the parameter as any or unknown. Both accept every array, and both lose the connection between input and output:

Parameter typeAccepts every inputResult typeChecked
anyyesanyno: mistakes compile
unknownyesunknownyes, but you must narrow before using it
T (generic)yestied to the inputyes, with the precise type

Type Argument Inference

Usually TypeScript infers the type arguments from the values you pass. You can also write them explicitly in angle brackets at the call, which is needed when nothing in the arguments mentions T, or when inference picks something you did not want.

Two inference results are worth knowing. pair(1, "one") without the explicit argument is an error: TypeScript infers T = number from the first argument and then reports Argument of type 'string' is not assignable to parameter of type 'number'. (TS2345). And emptyList() with no argument at all gets T = unknown, giving unknown[], which is rarely what you want.

Several Type Parameters

A function can declare as many type parameters as it needs, separated by commas. Each one is inferred independently.

mapValues infers three things at once: K is "tea" | "cake", V is number, and R is string from the callback's return. K extends string is a constraint, covered on the next page.

Generic Interfaces and Type Aliases

Types can take parameters too. You then write the argument when you use the type: Box<number>, ApiResponse<User>. Most built-in collection types work this way: Array<T>, Map<K, V>, Promise<T>, Record<K, V>.

Result<T, E = string> also shows a default type parameter: Result<number> means Result<number, string>. Like optional function parameters, parameters with defaults must come after the required ones.

Generic Classes

A class takes type parameters after its name, and every instance fixes them. Fields, methods and constructor parameters can all use them.

If the constructor takes a T, you can drop the explicit argument: new Box(5) infers Box<number>. Static members belong to the class itself, not to an instance, so they cannot use the class's type parameter (Static members cannot reference class type parameters., TS2302).

Generic Arrow Functions and Function Types

The type parameter list goes before the parameter list. The same syntax describes a generic function type.

const last = <T>(items: T[]): T | undefined => items[items.length - 1];

type Mapper = <T, R>(items: T[], fn: (item: T) => R) => R[];
const mapAll: Mapper = (items, fn) => items.map(fn);

// In a .tsx file, <T> looks like a JSX tag. Add a trailing comma:
const lastTsx = <T,>(items: T[]) => items[items.length - 1];

Inferring Literal Types With const Type Parameters

By default a generic infers widened types: pair("a", "b") gives [string, string]. When the exact values matter (route names, event names, column lists), mark the parameter const (TypeScript 5.0 and later) and it infers as if the argument were written as const:

The caller writes an ordinary array, and the function keeps the literal types.

When Not to Use Generics

A type parameter earns its place when it connects two things: a parameter and the return type, two parameters, or a parameter and a callback. If T appears only once, it adds nothing and a plain type is clearer.

// Pointless: T is used once, so it is just a longer way to write unknown
function logValue<T>(value: T): void {
  console.log(value);
}

// Clearer
function logValueSimple(value: unknown): void {
  console.log(value);
}

// Also pointless: returns T but nothing connects T to an argument,
// so the caller is really just asserting a type
function parseJson<T>(text: string): T {
  return JSON.parse(text);
}

The last one is common and misleading: parseJson<User>(text) looks type-safe but checks nothing, exactly like JSON.parse(text) as User. Return unknown and validate instead. Other signs of overuse: a type parameter that is always given the same argument, or one that could be replaced by a union of two known types.

For generics that need to know something about T (that it has a length, or that K is a key of T), constraints with extends are the next step.

Frequently Asked Questions

What are generics in TypeScript?

Generics are type parameters: placeholders such as T that are filled in with a real type each time a function, interface, type alias or class is used. function first<T>(items: T[]): T | undefined works for any array, and the result has the element type of the array you passed in, so first([1, 2]) is number | undefined and first(["a"]) is string | undefined.

What is the difference between generics and any?

any turns type checking off: the value going in and the value coming out have no relationship, and the result is any too. A generic keeps the relationship: identity<T>(x: T): T returns exactly the type you passed. Use unknown if you accept anything but do not need to hand the type back, and a generic if you do.

What does <T> mean in TypeScript?

It declares a type parameter named T. In function wrap<T>(value: T), T is a type variable that TypeScript fills in from the argument at each call, or that you pass explicitly as wrap<string>("a"). The name T is only a convention; longer names like TItem or Key work the same way.

How do I write a generic arrow function in TypeScript?

Put the type parameter list before the parameters: const first = <T>(items: T[]): T | undefined => items[0];. In a .tsx file <T> would be read as a JSX tag, so write <T,> with a trailing comma, or <T extends unknown>.

How do I set a default type for a generic in TypeScript?

Add = Type after the parameter: interface ApiResponse<T = unknown> { data: T }. Then ApiResponse with no argument means ApiResponse<unknown>. Parameters with defaults must come after the ones without, like optional function parameters.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED