Generics & Constraints: Beyond <T>
Generics are functions over types. Constraints are their parameter types. Mastering them eliminates entire categories of unsafe `any` usage and unlocks type-safe utility functions that preserve all the specificity of their inputs.
TypeScript Mastery
Generics & Constraints: Beyond <T>
Types are a specification language — not an annotation layer. Nowhere is that more evident than in generics. A function that accepts any and returns any has no specification at all — it is a black box. A function that accepts T and returns T makes a precise promise: whatever type you give me, I give you back. The difference between those two functions is the entire difference between a type-annotated codebase and a type-safe one. Generics preserve type information that any destroys.
1. What Generics Actually Are
A generic is a function over types. Just as a regular function takes value parameters and returns values, a generic type takes type parameters and returns types. This is not metaphorical — the analogy is exact.
1.1 Generic Functions — The Identity Function
TypeScript infers the type argument from the function's arguments. You rarely need to specify it explicitly: identity<string>('hello') and identity('hello') are identical when TypeScript can infer T.
1.2 Generic Types and Interfaces
Generics are not limited to functions — types and interfaces can be parameterized too:

2. Constraints — Bounding Type Parameters
A type parameter without a constraint accepts any type. Constraints narrow what is acceptable, enabling you to access properties and methods specific to the constraint type.
2.1 extends as a Constraint
extends in a generic constraint is not inheritance — it is a subset check. T extends Constraint means "T must be assignable to Constraint" — T must be a subset of the constraint's set. The word "extends" is the same keyword as in class inheritance, but the meaning is purely set-theoretic here.
2.2 keyof — Constraining to Valid Property Keys
The canonical use of constrained generics is type-safe property access:
The return type T[K] is an indexed access type — it looks up the type of key K in type T. This gives you the exact type of the property, not just unknown.
![Three-column table diagram showing constraint validation. Column headers: 'T', 'K extends keyof T', 'Valid call?'. Rows: Row 1: { name: string; age: number }, 'name', ✅ (green); Row 2: { name: string; age: number }, 'age', ✅; Row 3: { name: string; age: number }, 'email', ❌ (red, 'email' not in keyof T); Row 4: string[], number, ✅ (array index). Caption: 'K extends keyof T constrains K to the literal union of T's property names — invalid keys are caught at the call site'.](https://pub-74778554195b4df89d82f0d61988355a.r2.dev/assets/blog/frontend/typescript-generics-constraints-patterns/fig-02.png)
3. Default Type Parameters
Type parameters can have defaults, making generics ergonomic for common cases without sacrificing flexibility:
4. const Type Parameters (TypeScript 5.0)
By default, TypeScript widens literal types in generic inference. const type parameters prevent this:
![Two-row comparison. Top row labeled 'Without const type param': a code snippet shows makeArray(['a', 'b', 'c']). Arrow labeled 'T inferred as' points to string[] (with a yellow label 'widened — literal types lost'). Bottom row labeled 'With const type param': same call shows. Arrow labeled 'T inferred as' points to readonly ["a", "b", "c"] (with a green label 'literal tuple preserved'). Caption: 'const type parameters (TS 5.0) opt into literal inference — without them, array arguments always widen to their base element type'.](https://pub-74778554195b4df89d82f0d61988355a.r2.dev/assets/blog/frontend/typescript-generics-constraints-patterns/fig-03.png)
5. Common Anti-Patterns
5.1 any Where a Generic Would Suffice
5.2 Over-Constraining
Summary
| Concept | Rule |
|---|---|
| Generic function | <T> preserves type info from input to output; any destroys it |
extends constraint |
Subset check — T must be assignable to the constraint type |
K extends keyof T |
Constrains K to valid property names of T; use with T[K] for safe indexed access |
| Default type parameter | <T = string> provides an ergonomic default without sacrificing flexibility |
const type parameter |
Prevents literal widening on inference (TS 5.0+) |
| When to use generics | When you need to preserve the specific type of an input through to the output |
| When not to use generics | When you don't need to thread the type through — just use the concrete type |
What's Next
In Part 4, we cover Utility Types & Mapped Types — the standard library of type-level transformations. You'll read the source code of
Partial<T>,Required<T>, andReadonly<T>, then build deeper utilities likeDeepReadonly<T>andFlatten<T>from scratch.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.