Generics in TypeScript Explained with Real Examples
Generics let you write code that works with many types while keeping the connection between input and output types. Think of them as function parameters, but for types.
The problem
function first(items: any[]) { return items[0]; }
const n = first([1, 2, 3]); // any - we lost the type
The generic version
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(['a', 'b']); // string | undefined
TypeScript infers T from the argument, so you rarely write first<number>(...) yourself.
Constraints with extends
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('apple', 'fig'); // works for strings
longest([1, 2], [1]); // and arrays
longest(10, 20); // Error: number has no 'length'
keyof: type-safe property access
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
return items.map((item) => item[key]);
}
const users = [{ id: 1, name: 'Asha' }, { id: 2, name: 'Ravi' }];
pluck(users, 'name'); // string[]
pluck(users, 'email'); // Error: '"email"' is not assignable to '"id" | "name"'
A typed API client
async function api<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`/api${path}`, init);
if (!res.ok) throw new Error(`${res.status} ${path}`);
return res.json() as Promise<T>;
}
const projects = await api<Project[]>('/projects'); // Project[]
Note that this only tells TypeScript the shape; it doesn't check the response at runtime. Pair it with a validator like Zod for untrusted data.
Generic types and defaults
interface ApiResponse<T, E = string> {
data: T | null;
error: E | null;
}
type UserResponse = ApiResponse<User>; // E defaults to string
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.