TypeScript Generics
debt(d5/e3/b3/t5)
Closest to 'specialist tool catches it' (d5). The detection_hints list TypeScript compiler and ESLint as tools. The TypeScript compiler itself will flag cases where generics are misused (e.g., accessing properties on unconstrained T), but the common mistake of using `any` instead of a generic is not a compile error — it requires a specialist lint rule (e.g., @typescript-eslint/no-explicit-any) or deliberate code review to catch. It won't surface as a runtime error since `any` still 'works'.
Closest to 'simple parameterised fix' (e3). The quick_fix describes adding a type parameter <T> to functions and using extends to constrain — this is a small, localised change within a single function or file. It's more than a trivial one-liner (e1) since it requires updating the function signature and possibly the return type and call sites, but it doesn't span multiple files or components.
Closest to 'localised tax' (b3). The applies_to scope covers web and cli contexts broadly, but the debt of misusing generics (using `any` instead) is largely contained within the specific functions or modules where it occurs. It doesn't inherently shape the whole system architecture — it's a per-function productivity tax rather than a codebase-wide structural burden.
Closest to 'notable trap' (t5). The misconception field explicitly states that developers believe generics are only for library authors, causing them to reach for `any` instead. This is a well-documented gotcha that most developers encounter — competent devs familiar with other typed languages might expect type inference to handle this automatically without explicit type parameters, leading to the `any` pattern. It's a documented pitfall but not one that contradicts fundamentally how similar concepts work.
Also Known As
TL;DR
Explanation
Generics enable reusable, type-safe code. function identity<T>(arg: T): T preserves the type relationship between input and output. Constraints (<T extends object>) restrict what types are accepted. Generic interfaces (Array<T>, Promise<T>) and classes make collections type-safe. Conditional types (T extends string ? A : B) and infer enable type-level computation. Without generics, typed collections require either any (unsafe) or dozens of overloads.
Diagram
flowchart LR
subgraph Without_Generics
ANY[function identity any: any<br/>loses type information]
ANY_USE[const x = identity 42<br/>x is type any - unsafe]
end
subgraph With_Generics
GEN[function identity T T: T<br/>preserves type]
GEN_USE[const x = identity 42<br/>x is type number - safe]
end
subgraph Constraints
CONST[T extends HasLength<br/>restrict what T can be]
COND[T extends string number<br/>conditional types]
end
style ANY fill:#f85149,color:#fff
style GEN fill:#238636,color:#fff
style GEN_USE fill:#238636,color:#fff
Common Misconception
Why It Matters
Common Mistakes
- Using any instead of a generic when the function should preserve type: function first(arr: any[]) vs function first<T>(arr: T[]): T.
- Over-constraining generics — <T extends string> when <T> would work, unnecessarily limiting callers.
- Not using the return type from generics — the whole point is that the caller gets back the specific type they passed in.
- Unconstrained generics accessing properties — T has no known properties; use <T extends {id: number}> if you need id.
Code Examples
// Loses type information — always returns any:
function first(arr: any[]): any {
return arr[0];
}
const name = first(['Alice', 'Bob']); // name is 'any' — no type safety
name.toUpperCase(); // OK but also: name.nonExistentMethod() — no error
// Generic — preserves type:
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const name = first(['Alice', 'Bob']); // name is 'string | undefined'
name?.toUpperCase(); // ✅ string method
name?.nonExistent(); // ❌ TypeScript error — doesn't exist on string
// With constraint:
function getById<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}