Technology · TypeScript
TypeScript Generics
Master generics to build reusable, type-safe functions, classes, and components in TypeScript.
TL;DR
- 01Use type parameters to write reusable, type-safe code.
- 02Constrain generics to restrict what types they accept.
- 03Provide default types to simplify common generic usage.
Basics
- Add a type parameter in angle brackets to make a function generic.
function identity<T>(value: T): T { return value; } - The letter
Tis a placeholder that gets replaced with a real type. - TypeScript infers the type from the argument you pass to the function.
identity("hello"); // T inferred as string identity(42); // T inferred as number - You can also pass the type explicitly using angle brackets at the call.
identity<string>("hello"); - Use single uppercase letters or short descriptive names for type parameters.
Generic Functions
- Write a generic function once and reuse it for many different types.
function first<T>(items: T[]): T | undefined { return items[0]; } - Return values keep their original type, unlike using
anyfor parameters. - Pass arrays generically with
T[]orArray<T>for typed list operations. - Use multiple type parameters when handling pairs of types.
function pair<T, U>(a: T, b: U): [T, U] { return [a, b]; } - Chain generics through helper functions to keep type information intact.
Constraints
- Limit a type parameter using
extendsto require certain properties.function byId<T extends { id: number }>(items: T[], id: number) { return items.find(i => i.id === id); } - Constraints make sure the type has the properties your function needs.
- Use
keyof Tto constrain a parameter to keys of another type.function getValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } - Combine constraints with conditional types for advanced type narrowing.
- Constraints still allow types that include extra properties beyond the requirement.
Default Types
- Set a default type to simplify common use cases for callers.
interface ApiResponse<T = unknown> { data: T; status: number; } - Default types let callers skip the type argument when the default fits.
const res: ApiResponse = { data: null, status: 200 }; - Combine defaults with constraints to keep types both safe and easy.
type Config<T extends object = {}> = { values: T }; - Use defaults to keep public APIs clean while supporting custom overrides.
- Defaults are especially useful for generic React components and utility types.
Generic Classes
- Add type parameters to class names for typed instances and methods.
class Box<T> { constructor(public value: T) {} get() { return this.value; } } - Use the type parameter inside properties, methods, and constructor signatures.
- Pass the type when creating an instance, or let it be inferred.
const a = new Box<string>("hello"); const b = new Box(42); // inferred as Box<number> - Generic classes work well for collections, queues, stacks, and state stores.
- TypeScript will often infer the type from the constructor argument automatically.
Tip: Name generics meaningfully, like
TItemorTResponse, when single letters make the code harder to read.
Warning: Avoid overusing generics in simple functions, since they can make code harder to read and maintain.