Suppose you need firstOf(items) to work on both number[] and string[], and the tempting shortcut is to type the parameter as any[].
The problem is that the return value becomes any, so type checking is lost exactly where you need it. As lesson 2-3 established, any switches checking off. firstOf would accept anything and return any, so misuse like firstOf([1, 2]).toUpperCase() compiles cleanly and then crashes at runtime.
That is the worst of both worlds: the function is flexible, but the flexibility costs you the guarantee. Generics solve the same problem without losing the types, which is what this unit is about.
The problem generics solve
Here is a function that works for any element type:
function firstOf(items: number[]): number { return items[0]; }
For strings you would need a copy with string[]. Copies for every type, all identical bodies. The any escape hatch kills checking. What you want to say is: "this works for some type, call it T, and whatever array of T comes in, one T comes out".
That is exactly a generic function:
function firstOf<T>(items: T[]): T { return items[0]; }
<T> declares a type parameter, a placeholder filled in at each call. Call it with [10, 20, 30] and T becomes number for that call. Call it with strings and T becomes string. One body, full checking, no copies.
One generic function, two element types
function firstOf<T>(items: T[]): T { return items[0]; } const n = firstOf([10, 20, 30]); const s = firstOf(["alpha", "beta"]); console.log(n + 5); console.log(s.toUpperCase());
Output
15
ALPHAn is a number and s is a string, both inferred, which is why n + 5 and s.toUpperCase() each compile. The single function body served two different types without any loss of precision.
Two things to take from this
- Writing
firstOf<number>([10, 20, 30])explicitly is legal but almost never necessary. TypeScript infersTfrom the argument, so generic calls usually look exactly like ordinary calls. firstOf([1, 2]).toUpperCase()is a compile error, which is the whole contrast with theany[]version. Checking survives the trip through the generic function.
lastOf: the same pattern with a different index
lastOf<T> returns the final element of any array, reading the element at index items.length - 1.
function lastOf<T>(items: T[]): T { return items[items.length - 1]; } console.log(lastOf([1, 2, 3])); console.log(lastOf(["x", "y", "z"]).toUpperCase());
Output
3
ZReading the generic signature
- The shape copies
firstOfexactly, changing only the index expression.<T>goes after the function name, the parameter isT[], and the return type isT. - The second call chains
.toUpperCase()becauseTwas inferred asstringthere. On the first callTisnumber, and the same chained method would be rejected. - The type parameter connects input to output. That link is the thing
any[]throws away and generics preserve.