Generics: Reusable Typed Code
Generics let one function or type work with many types while staying fully type-safe.
What you will learn
- Understand the problem generics solve
- Write a generic function with a type parameter
- See a generic that keeps the exact type
The problem: one function, many types
Say you want a function that returns the first item of any array. Without generics you would either write one per type, or use any and lose all safety:
// The unsafe way — any throws away the type
function firstAny(items: any[]): any {
return items[0];
}
const n = firstAny([1, 2, 3]);
n.toUpperCase(); // no error... but n is a number! crash at runtimeNote: Output:
(Compiles, then CRASHES: n.toUpperCase is not a function.)
Because the return type is any, TypeScript cannot warn us. We need it to remember the real type.
The fix: a type parameter
A generic adds a type parameter, written in angle brackets — usually <T> (T for "Type"). It is a placeholder that gets filled in with the real type each time the function is called.
function first<T>(items: T[]): T {
return items[0];
}
const num = first([1, 2, 3]); // T becomes number
const word = first(['a', 'b', 'c']); // T becomes string
console.log(num + 10); // number maths, ok
console.log(word.toUpperCase()); // string method, okNote: Output:
11
A
For numbers, T is number, so num + 10 works. For strings, T is string, so .toUpperCase() works. One function, full type safety for both.
Calling the wrong method is now caught
function first<T>(items: T[]): T {
return items[0];
}
const num = first([1, 2, 3]); // T is number
num.toUpperCase(); // error — number has no toUpperCaseNote: Output:
Error: Property 'toUpperCase' does not exist on type 'number'.
Unlike the any version, the generic kept the real type, so the wrong method is caught at compile time.
Generics in types you already use
You have actually seen generics before. Array<string> is a generic — Array filled with string. Promise<number> is a promise that resolves to a number. The angle brackets pass a type in.
Array<T>— a list of T.Promise<T>— an async value of type T (async means a value that arrives later, such as the result of a download).Map<K, V>— a map from keys of type K to values of type V.
Tip: Read <T> as "a type I will fill in later". The huge win: write code once, use it with any type, and never lose type checking.
Q. Why use a generic <T> instead of any?
✍️ Practice
- Write a generic
last<T>(items: T[]): Tthat returns the last element, and call it with numbers and strings. - Write a generic
wrap<T>(value: T): T[]that returns a one-item array.
🏠 Homework
- Write a generic
identity<T>(value: T): Tfunction, call it three times with different types, and show each output.