# Generics Best Practices
## Rule
Use generics only when a function/type truly operates on variable types. Always constrain generics with extends. Prefer built-in utility types over custom implementations. Name type parameters meaningfully for complex generics.
## Good Examples
```typescript
// Constrained generic
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Generic with default
interface PaginatedResponse<T = unknown> {
data: T[];
total: number;
page: number;
pageSize: number;
}
// Generic class
class Repository<T extends { id: string }> {
private items = new Map<string, T>();
save(item: T): void {
this.items.set(item.id, item);
}
findById(id: string): T | undefined {
return this.items.get(id);
}
findAll(): T[] {
return [...this.items.values()];
}
}
// Utility type composition
type CreateDTO<T> = Omit<T, "id" | "createdAt" | "updatedAt">;
type UpdateDTO<T> = Partial<CreateDTO<T>>;
// Conditional types
type ApiResult<T> = T extends void
? { success: true }
: { success: true; data: T };
```
## Bad Examples
```typescript
// BAD: Unnecessary generic — always string
function stringify<T>(value: T): string {
return String(value);
}
// Just use: function stringify(value: unknown): string
// BAD: Unconstrained generic
function merge<T, U>(a: T, b: U): T & U {
return { ...a, ...b }; // Doesn't work for non-objects!
}
// Constrain: <T extends object, U extends object>
// BAD: Single-letter names for complex generics
function transform<T, U, V, W>(a: T, b: U, c: V): W { ... }
// Use meaningful names: <TInput, TOutput, TConfig, TResult>
// BAD: Reimplementing utility types
type MyPartial<T> = { [K in keyof T]?: T[K] };
// Just use: Partial<T>
```
## Enforcement
- ESLint: @typescript-eslint/no-unnecessary-type-parameters
- Code review: verify generic constraints are present
- Prefer utility types: Partial, Required, Pick, Omit, Record