TypeScript generics
Generics let a function or type work over many types while preserving the relationships between them. Used well, they make a signature both flexible and precise; used for their own sake, they turn a readable function into an unreadable puzzle of type parameters.
Method
- Reach for a generic when the output type depends on the input
type.
identity<T>(x: T): T,first<T>(arr: T[]): T | undefined: the caller's type flows through. If the function returns a fixed type regardless of input, it does not need a generic; a plain parameter type is clearer. - Design for inference, not annotation. The best generics are ones
callers never have to specify: TypeScript infers
Tfrom the arguments. Order and shape parameters so inference works (map<T, U>(arr: T[], fn: (x: T) => U): U[]infers both from usage). If callers constantly writefn<SomeType>(...), the design is fighting inference. - Constrain with
extendsto say what T must support.<T extends { id: string }>lets the body use.idand rejects types that lack it. Constraints turn a generic from "any type" into "any type that fits", which is both safer and better-documented. Usekeyof, conditional types, and mapped types where the relationship genuinely needs them. - Stop before the type gymnastics. Deeply nested conditional and
mapped types that take an afternoon to read are a cost every future
maintainer pays. If a generic signature needs a comment to explain
what it does, consider whether a simpler type, an overload, or just
unknownat the boundary would serve better (see cognitive-load). - Prefer
unknownoveranyat the untyped edges. When a value's type truly is not known (parsed JSON, external input),unknownforces a check before use, whereanydisables the compiler silently. Narrow it explicitly (see typescript-narrowing). - Name type parameters meaningfully past the trivial case.
TandUare fine for a genericmap;TData,TError,TKeyread far better in a real API's multi-parameter signature.
Boundaries
- Generics are a design tool for library and shared-utility code; most application code needs few of them. Do not genericize a function used in one place.
- Type-level cleverness has zero runtime effect and real readability cost; it is worth it for a widely-used API's ergonomics, rarely for internal code.
- When inference produces a wrong or too-wide type, a targeted overload or an explicit return type often beats escalating the generic's complexity (see ts-api-types).