TypeScript Generics Explained
Generics let you write flexible code without sacrificing type safety. Let's understand them step by step.
Why Generics?
Without generics, you'd have to write multiple functions:
function identityString(arg: string): string {
return arg;
}
function identityNumber(arg: number): number {
return arg;
}
With generics, one function handles all types:
function identity<T>(arg: T): T {
return arg;
}
identity<string>("hello"); // Type: string
identity<number>(42); // Type: number
identity("inferred"); // Type: string (inferred)
Generic Interfaces
Create flexible data structures:
interface Box<T> {
value: T;
getValue(): T;
}
const stringBox: Box<string> = {
value: "hello",
getValue() { return this.value; }
};
Generic Constraints
Limit what types are acceptable:
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): number {
console.log(arg.length);
return arg.length;
}
logLength("hello"); // OK - strings have length
logLength([1, 2, 3]); // OK - arrays have length
logLength(123); // Error - numbers don't have length
Multiple Type Parameters
Use multiple generics together:
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const result = pair("age", 25); // Type: [string, number]
Generic Classes
Build reusable class patterns:
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
Real-World Example: API Response
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface User {
id: number;
name: string;
}
async function fetchUser(): Promise<ApiResponse<User>> {
// ...
}
Conclusion
Generics are essential for writing professional TypeScript. They give you flexibility while maintaining type safety.