Back to TypeScript tutorials
Intermediate14 min read

Generics

Write reusable, type-safe functions, classes, and interfaces that work across multiple types.

Generic Functions

Type parameters (e.g., `<T>`) let functions operate on varied types while preserving relationships between inputs and outputs. `function first<T>(arr: T[]): T | undefined` ensures the return type matches the array element type.

Explicit type arguments (`first<number>([1,2])`) are rarely needed — inference from arguments usually suffices. Specify explicitly when the compiler cannot infer from context alone.

  • Type params link input and output types
  • Inference usually avoids explicit args
  • Generics replace unsafe `any`
function wrap<T>(value: T): { value: T } {
  return { value };
}

Generic Interfaces and Classes

Generic interfaces model containers and responses: `interface ApiResponse<T> { data: T; meta: Pagination }`. Generic classes wrap repositories and data structures: `class Stack<T> { push(item: T): void {} }`.

Default type parameters provide fallbacks: `interface Paginated<T = unknown>`. This keeps generic APIs usable without forcing type arguments at every callsite.

  • Generic interfaces for containers
  • Generic classes for typed data structures
  • Default type parameters for ergonomics
interface Repository<T> {
  findById(id: string): Promise<T | null>;
  save(entity: T): Promise<T>;
}

Constraints with extends

Constrain type parameters to ensure required properties exist: `function logId<T extends { id: string }>(item: T)`. Constraints enable accessing properties inside the function body with type safety.

Use `keyof T` constraints for property access helpers: `function getProp<T, K extends keyof T>(obj: T, key: K): T[K]`. This pattern powers type-safe pick and get utilities.

  • `extends` constrains type parameters
  • `keyof` for property-safe access
  • Enables generic utility functions
function pluck<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

Conditional Types

Conditional types select types based on conditions: `T extends U ? X : Y`. They enable advanced utility types like `Exclude`, `Extract`, and custom mapped transformations.

The `infer` keyword extracts types within conditional types — for example, inferring the resolved type of a Promise. These patterns appear in library typings more than application code but unlock powerful abstractions.

  • `T extends U ? X : Y` pattern
  • `infer` extracts types in conditionals
  • Foundation of built-in utility types
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

Generics in Production APIs

Design generic APIs with sensible constraints and defaults — overly loose generics provide no safety; overly tight ones frustrate consumers. Document type parameters in public library exports.

Avoid generic explosion (too many type parameters on one function). Split complex generic functions into simpler composed helpers. Test generic utilities with multiple type arguments in unit tests to verify inference.

  • Balance safety and ergonomics
  • Document public generic APIs
  • Test inference with varied type args

Get In Touch


Ready to discuss your next project? Drop me a message.