TypeScript Generics for Real Devs
Stop copy-pasting types. A practical guide to generics, conditional types, and utility types that will make your codebase 10x more maintainable.
The problem generics solve
Without generics you end up with three bad options: use any (unsafe), copy-paste the type for every variant (noisy), or write overloads (verbose). Generics give you a fourth option: write it once and let the caller specify the type.
Generic functions
The simplest generic is a function that returns whatever type it receives. The <T> is a type parameter — think of it as a variable, but for types.
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // inferred: number | undefined
const s = first(['a', 'b']); // inferred: string | undefinedConstraints with extends
You can constrain what T is allowed to be. This lets you access properties on T that TypeScript can guarantee exist.
function getLabel<T extends { label: string }>(item: T): string {
return item.label; // safe — T must have label
}Conditional types
Conditional types let you express if-else logic at the type level. They're the basis for most advanced utility types in TypeScript's standard library.
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // falseUtility types worth knowing
- Partial<T> — makes all properties optional
- Required<T> — makes all properties required
- Pick<T, K> — creates a type with only the listed keys
- Omit<T, K> — creates a type without the listed keys
- ReturnType<F> — extracts the return type of a function
- Awaited<T> — unwraps a Promise type
A well-typed codebase isn't one where every variable has an explicit annotation. It's one where the types are accurate, narrow, and do real work — catching bugs before you run the code.