An unconstrained generic <T> is effectively <T extends unknown> — it accepts any value, and TypeScript cannot enforce meaningful relationships between T and the function's behavior. The symptom is forced casts inside the generic body: if you need (item as any).id, the generic is not constrained correctly. ISO 25010:2011 §6.5.4 classifies this as a type-safety defect. Unconstrained generics in utility code are particularly insidious because they look safe from the outside while silently bypassing type checks inside.
Medium because unconstrained generics defeat the purpose of generic typing — they provide an API that looks type-safe but requires internal casts to work, masking real type errors.
Add extends constraints to generics that access specific properties or operate on values with known shapes. This eliminates the internal casts that unconstrained generics require:
// Bad: unconstrained T, forced cast to access 'id'
function findById<T>(items: T[], id: string): T | undefined {
return items.find((item) => (item as any).id === id)
}
// Good: constraint ensures T has 'id'
function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id) // no cast needed
}
// Good: keyof constraint for property access
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
ID: code-quality-essentials.organization.generic-constraints
Severity: medium
What to look for: Search source files for generic type parameters (<T>, <K>, <V>). For each usage, check whether the generic is constrained with an extends clause. An unconstrained <T> is effectively <T extends unknown>, meaning the generic function accepts any value and TypeScript cannot enforce meaningful type relationships. Well-constrained generics look like: <T extends object>, <T extends Record<string, unknown>>, <K extends keyof T>, <T extends BaseModel>. Look especially at utility functions, API response handlers, data transformation functions, and React component props that use generics. Unconstrained generics in utility code are a signal the developer was trying to avoid writing a proper type.
Pass criteria: Enumerate all relevant code locations. Generic functions use extends constraints that accurately describe what types are accepted. Unconstrained T only appears in utility types where any value is genuinely valid (e.g., identity function) with at least 1 verified location.
Fail criteria: Generics used liberally without constraints, effectively allowing any value and defeating the purpose of typing.
Skip (N/A) when: Project has no generics used anywhere (no generic functions, no generic types).
Detail on fail: "Unconstrained generics found in utility functions; T used without extends clause where a constraint would improve type safety"
Remediation: Add constraints to generics that accept values used in specific ways:
// Bad: unconstrained T, no guarantee T has an 'id' field
function findById<T>(items: T[], id: string): T | undefined {
return items.find((item) => (item as any).id === id) // forced cast
}
// Good: constraint ensures T has 'id'
function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
return items.find((item) => item.id === id) // no cast needed
}
// Good: keyof constraint for property access
function getProperty<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}