Generics let you write functions, interfaces, and classes that work with any type while keeping full type safety. Every time you write Array<string>, Promise<Response>, or useState<number>(), you're using generics. They're the mechanism that makes code both reusable and type-safe — without forcing you to choose between any (flexible, unsafe) and hardcoded types (safe, rigid).
The core idea is simple: instead of specifying a concrete type, you declare a type parameter — a placeholder that gets filled in when the generic is used. TypeScript then flows that type through the entire function or interface, catching mismatches at compile time.
Master typescript generics
Take the TypeScript Essentials course with hands-on lessons and challenges.
The type parameter T captures whatever type the array contains. TypeScript infers T from the argument — no need to write firstElement<number>([1, 2, 3]).
This is the pattern most backend and frontend codebases use. The caller specifies the expected response shape, and TypeScript validates all property access downstream.
The constraint T extends HasId guarantees every item has an id property, but the full type (with name and role) is preserved in the return type. The constraint is a floor, not a ceiling.
K extends keyof T constrains the key to valid property names. The return type T[K] resolves to the exact type of that property. This pattern is everywhere — lodash's _.get, form libraries, ORM select queries.
The const modifier tells TypeScript to infer the narrowest possible type. Before TS 5.0, callers had to write 'as const' everywhere. Now the function author handles it once. Use this in builder functions, route definitions, and configuration factories.
Every ORM (Prisma, TypeORM, Drizzle) uses this pattern internally. The base class provides typed CRUD for any entity. Subclasses add entity-specific queries without duplicating the common operations.
Using `any` when a function works with multiple types — this throws away all type checking
Replace `any` with a type parameter. If you write `function wrap(value: any): any`, change it to `function wrap<T>(value: T): T`. The type flows through and catches bugs.
Explicitly specifying type arguments when TypeScript can infer them — e.g. `firstElement<number>([1, 2, 3])`
Let TypeScript infer: `firstElement([1, 2, 3])`. Only specify type arguments when inference fails or is ambiguous, like `fetchApi<User>("/api/users")` where the return type can't be inferred from the argument.
Accessing properties on an unconstrained type parameter — e.g. `value.length` when T could be anything
Add a constraint: `<T extends { length: number }>`. This tells TypeScript what properties T is guaranteed to have, so the access is safe.
Over-constraining generics with properties the function body never uses
Only constrain what you actually access. If the function doesn't use `.length`, don't require it. Unnecessary constraints reject valid inputs for no reason.
Generics are type parameters that make code reusable without sacrificing type safety. TypeScript infers them from usage in most cases. Constraints (`extends`) define the minimum shape a type must have, while `keyof` constraints enable type-safe property access. Const type parameters (TS 5.0+) infer literal types automatically. In practice, you'll use generics most often in API wrappers, utility functions, data structures, and base classes.
Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.
Interactive lessons and challenges, right in your code editor.
Check the free courses. No credit card.