generics
The Problem Generics Solve
Imagine writing a function that returns whatever you pass into it, for any type:
function identity(value: any): any {
return value;
}This works, but it loses all type safety — the return type is any, so TypeScript can't help you afterward. Generics solve this by letting a function, class, or interface work with any type while still preserving type information.
A Generic Function
function identity(value: T): T {
return value;
}
let output1 = identity("Hello"); // output1 is typed as string
let output2 = identity(42); // output2 is typed as number T is a type parameter — a placeholder that gets filled in with a real type whenever the function is called. TypeScript can usually infer T automatically:
let output3 = identity("Hello"); // T inferred as string, no need to specify itGeneric Functions with Arrays
function firstElement(arr: T[]): T {
return arr[0];
}
const firstNum = firstElement([1, 2, 3]); // number
const firstName = firstElement(["a", "b", "c"]); // string Multiple Type Parameters
function pair(first: A, second: B): [A, B] {
return [first, second];
}
const result = pair("age", 30); // ["age", 30] Generic Interfaces
interface Box {
contents: T;
}
const stringBox: Box = { contents: "Hello" };
const numberBox: Box = { contents: 42 }; Generic Classes
class DataStore {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getAll(): T[] {
return this.items;
}
}
const stringStore = new DataStore();
stringStore.add("apple");
stringStore.add("banana");
console.log(stringStore.getAll()); // ["apple", "banana"] Constraining Generics with extends
Sometimes you want to allow any type, but only if it has certain properties. Use extends to add a constraint.
interface HasLength {
length: number;
}
function logLength(item: T): void {
console.log(item.length);
}
logLength("Hello"); // valid, strings have a length property
logLength([1, 2, 3]); // valid, arrays have a length property
logLength(42); // Error: number doesn't have a 'length' property Default Type Parameters
interface Box {
contents: T;
}
const defaultBox: Box = { contents: "text" }; // T defaults to string Why Generics Matter
| Without Generics | With Generics |
|---|---|
Use any, losing type safety | Types stay accurate and specific per call |
| Write duplicate functions for each type | Write one flexible, reusable function |
| No autocomplete or type checking after the fact | Full autocomplete and error checking preserved |
Coming up next: Union and intersection types, plus type narrowing — handling values that could be more than one type.