Generic types
So far only functions had a type parameter. But a container in the port has its label holder from the start: containers are made from one design, and the cargo and the label are decided later. Types need the same.
Imagine that a program keeps values in a wrapper { value: … }: sometimes a number, sometimes a string. Without a type parameter you would write a separate type for each case: type NumberBox = { value: number }, type StringBox = { value: string } and so on. The shape is the same, only the type inside differs—the same problem as with the function first.
An alias with a type parameter
A generic type declares a type parameter in angle brackets after its name, as a function does:
type Box<T> = { value: T };
const age: Box<number> = { value: 30 };
const nick: Box<string> = { value: "ann" };
console.log(age.value + 1, nick.value); // 31 annLet's take the declaration apart:
type Box<T>declares a type alias with the parameterT;{ value: T }is the object shape, withTin place of the property's type;Box<number>is the type with an argument:numberis put in place ofT, which gives the shape{ value: number }.
For a function, T is inferred from the arguments of a call. A type has no call, so you write the type argument yourself: Box<number>, Box<string>. The program prints 31 ann: age.value has the type number, and you can add one to it.
A generic interface
An interface can have a type parameter too. Server responses are often described this way: the wrapper is the same, and the data inside differs:
interface User {
id: number;
nick: string;
}
interface ApiResponse<T> {
ok: boolean;
data: T;
}
const res: ApiResponse<User> = {
ok: true,
data: { id: 1, nick: "ann" },
};
console.log(res.data.nick); // annApiResponse<User> is a response whose data field holds a User. The same interface describes a list as well: ApiResponse<User[]>.
Here the container picture ends. You cannot put a real container inside another, but a type argument can be another generic type: Box<Box<number>> is a box holding a box with a number.
Interviewers often give this task: describe a server response whose wrapper is shared by all requests while the data differs. The expected solution is a generic interface like ApiResponse<T>.
Array<T> is a generic type too
Now you can read Array<number> to the end. In TypeScript's standard library an array is described as interface Array<T>, and its methods speak of T:
push(...items: T[]): numbertakes elements of typeT;pop(): T | undefinedreturns an element of typeT, orundefinedif the array is empty;length: numberdoes not depend onT.
number[] is the short form of the same Array<number>. In it T is number, which is why [1, 2].pop() has the type number | undefined.
A default type
A type parameter can have a default value, written with =, like a function parameter:
type Field<T = string> = { label: string; value: T };
const city: Field = { label: "City", value: "Paris" };
const year: Field<number> = { label: "Year", value: 2026 };
console.log(city.value, year.value); // Paris 2026Let's go line by line:
<T = string>says: if no type argument is written,Tisstring.cityhas no argument, and the editor's hover shows the typeField<string>.yearhas an argument written, so the default is not used: the type isField<number>.- The program prints
Paris 2026.
Common mistake
A generic type is used without an argument:
type Box<T> = { value: T };
const box: Box = { value: 1 }; // ✗error TS2314: Generic type 'Box' requires 1 type argument(s).The message says: the generic type Box needs one type argument. It is like shipping a container with an empty label holder: nobody knows which cargo to check it against. TypeScript will not infer T from the value { value: 1 }, because a type has no call. Write the argument, Box<number>, or give the parameter a default: type Box<T = number>.
Key points
- A generic type declares its parameter in angle brackets:
type Box<T> = { value: T }orinterface ApiResponse<T>. - For a type,
Tis not inferred; you write the argument yourself:Box<number>. Array<T>is a generic interface of the standard library, andnumber[]is the short form ofArray<number>.- A default
<T = string>lets you omit the argument:FieldmeansField<string>.