Writing a function that returns the first element of an array takes one line. Making it useful for numbers and for invoices alike, without losing the type along the way, takes something more.
That is what a generic is for: leaving the type as a parameter, and recovering it at the call site.
Definition
A generic is a type parameter. It is declared between angle brackets, next to the function name, and behaves like a variable whose value is a type, settled at the moment of the call.
function first<T>(list: T[]): T | undefined {
return list[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefinedNobody wrote first<number>: the compiler works T out from the argument it received. Without a generic the return would have been annotated any, and the whole verification chain would have collapsed after that call.
Constraining the type parameter
A free T allows no operation at all, since it could be anything. The extends keyword restricts the set of accepted types, and unlocks whatever becomes safe as a result.
function byKey<T, K extends keyof T>(object: T, key: K): T[K] {
return object[key];
}
const invoice = { id: 42, amount: 19 };
const m = byKey(invoice, "amount"); // number
byKey(invoice, "client");
// Argument of type '"client"' is not assignable
// to parameter of type '"id" | "amount"'.Two type parameters are enough here to express a rule the language alone cannot state: the requested key exists on the object, and the return has the type of that precise key.
The generics already everywhere
| Notation | What the parameter stands for |
|---|---|
| Array<T> | The type of the elements of an Array |
| Promise<T> | The value a fulfilled Promise will hand back |
| Map<K, V> | The key type then the value type of a Map |
| Record<K, V> | An object whose keys and values are both announced |
| Partial<T> | The same type, with every property turned optional |
These five notations show up within the first hour of TypeScript. Understanding generics mostly means no longer reading them as magic syntax.
Frequently asked questions
Why not simply write any?
Because any switches checking off instead of carrying it forward. A function taking any and returning any lets every downstream typo through. A generic keeps the link between what goes in and what comes out, which is precisely the useful piece of information.
How many type parameters are reasonable?
One in the vast majority of cases, two when a correspondence is involved, such as a key and its value. Past three, the signature becomes harder to read than the problem it solves, and a named interface almost always does better.
Must the type be spelled out at every call?
Rarely. Inference covers most situations, and a hand-written type between angle brackets often signals that the signature lacks a constraint. The legitimate case is a call with no argument carrying information, such as a request where only the return is typed.