Generics
Write reusable typed functions and interfaces with type parameters — the `<T>` pattern explained
Free tier: read the explanation here. Upgrade to Pro for Drills, Speed challenges & Mastery badges.
UpgradeKnowledge Debt detected
You can study this freely — but your score may plateau if these foundations have gaps. The Mastery badge requires them to be solid.
Explanation
Generics let you write functions and types that work with *any* type while preserving type information. Think of T as a placeholder that gets filled in at call time.
The problem generics solve:
typescript // Without generics — loses type information function identity(val: any): any { return val; } const result = identity(42); // result is 'any' — useless // With generics — type is preserved function identity<T>(val: T): T { return val; } const result = identity(42); // result is 'number' const str = identity('hello'); // result is 'string'
Generic functions:
typescript function firstItem<T>(arr: T[]): T | undefined { return arr[0]; } const first = firstItem([1, 2, 3]); // type: number | undefined const name = firstItem(['Alice', 'Bob']); // type: string | undefined
Generic interfaces:
typescript interface ApiResponse<T> { data: T; status: number; message: string; } // Use it with any data shape const userResponse: ApiResponse<User> = { data: { id: 1, name: 'Alice' }, status: 200, message: 'OK', };
Type constraints — restrict what types T can be: ``typescript // T must have a .length property function logLength<T extends { length: number }>(val: T): void { console.log(val.length); } logLength('hello'); // OK — string has .length logLength([1, 2, 3]); // OK — array has .length // logLength(42); // Error — number has no .length // T must be a key of object U function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const name = getProperty({ name: 'Alice', age: 30 }, 'name'); // string
Multiple type parameters:
typescript function zip<A, B>(a: A[], b: B[]): [A, B][] { return a.map((item, i) => [item, b[i]]); } zip([1, 2], ['a', 'b']); // [number, string][]
Examples
Generic function and interface
Generics let you write `Stack<T>` once and use it as `Stack<number>`, `Stack<string>`, `Stack<User>` — full type safety for each.
// Generic stack — works for any element type
interface Stack<T> {
push(item: T): void;
pop(): T | undefined;
peek(): T | undefined;
size(): number;
}
class TypedStack<T> implements Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }
size(): number { return this.items.length; }
}
const numStack = new TypedStack<number>();
numStack.push(1);
numStack.push(2);
console.log(numStack.pop()); // 2 — TypeScript knows this is number
const strStack = new TypedStack<string>();
strStack.push('hello');
// strStack.push(42); // Error: Argument of type 'number' is not assignable to type 'string'Constrained generics with keyof
`extends` on a type parameter is a constraint, not inheritance — it says "T must have at least these properties." `keyof T` produces a union of all property names of T.
// Constraint: T must have a .length property
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
console.log(longest('hello', 'hi')); // 'hello'
console.log(longest([1, 2, 3], [4, 5])); // [1, 2, 3]
// console.log(longest(1, 2)); // Error: 'number' has no 'length'
// keyof — pick a property safely
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map(item => item[key]);
}
const users = [
{ id: 1, name: 'Alice', age: 30 },
{ id: 2, name: 'Bob', age: 25 },
];
const names = pluck(users, 'name'); // string[]
const ages = pluck(users, 'age'); // number[]
// pluck(users, 'email'); // Error: 'email' is not a key of the object typeHow well did you understand this?
Next in TypeScript
Type Narrowing