Generics

Med

Generics let you write reusable code that works with any type while preserving type safety. Instead of using `any`, you define type parameters (like `<T>`) that act as placeholders. The actual type is filled in when the generic is used, so TypeScript can still check everything at compile time. Generics appear in functions, interfaces, classes, and type aliases.

Interactive Visualization

Key Points

  • Generics use angle brackets `<T>` to declare type parameters
  • Type parameters act as placeholders that are filled in at usage site
  • Constraints (`extends`) limit what types a generic can accept
  • Default type parameters (`<T = string>`) provide fallback types
  • Multiple type parameters (`<K, V>`) are common in maps and key-value patterns
  • Generic inference lets TypeScript determine `T` from function arguments automatically
  • Generics preserve the relationship between input and output types

Code Examples

Generic Functions

// Identity function — preserves the exact input type
function identity<T>(value: T): T {
  return value
}

const str = identity('hello')  // inferred as string
const num = identity(42)       // inferred as number

// Generic with constraint
function getLength<T extends { length: number }>(item: T): number {
  return item.length
}

getLength('hello')    // OK — string has length
getLength([1, 2, 3])  // OK — array has length
// getLength(42)      // Error — number has no length

// Multiple type parameters
function mapObject<K extends string, V, R>(
  obj: Record<K, V>,
  fn: (value: V, key: K) => R
): Record<K, R> {
  const result = {} as Record<K, R>
  for (const key in obj) {
    result[key] = fn(obj[key], key)
  }
  return result
}

Generic functions infer their type parameters from arguments. Constraints (extends) restrict what types are acceptable, ensuring the generic body can safely use specific properties.

Generic Interfaces & Classes

// Generic interface
interface Repository<T> {
  findById(id: string): T | undefined
  findAll(): T[]
  save(item: T): void
}

// Generic class
class Stack<T> {
  private items: T[] = []

  push(item: T): void {
    this.items.push(item)
  }

  pop(): T | undefined {
    return this.items.pop()
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1]
  }

  get size(): number {
    return this.items.length
  }
}

const numStack = new Stack<number>()
numStack.push(1)
numStack.push(2)
const top = numStack.pop() // type is number | undefined

Generic interfaces and classes parameterize their type at the class/interface level, so all methods share the same type parameter throughout.

Default & Constrained Generics

// Default type parameter
interface ApiResponse<T = unknown> {
  data: T
  status: number
  message: string
}

// No type argument needed — defaults to unknown
const res: ApiResponse = { data: null, status: 200, message: 'OK' }

// With explicit type
const userRes: ApiResponse<{ name: string }> = {
  data: { name: 'Alice' },
  status: 200,
  message: 'OK',
}

// keyof constraint — ensures K is a valid key of T
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>
  for (const key of keys) {
    result[key] = obj[key]
  }
  return result
}

const user = { name: 'Alice', age: 30, email: 'a@b.com' }
const subset = pick(user, ['name', 'email'])
// type: { name: string; email: string }

Default type parameters reduce boilerplate when a common type is expected. The `keyof` constraint is a powerful pattern for ensuring type-safe property access.

Common Mistakes

  • Using `any` instead of a generic when the type should be preserved
  • Over-constraining generics with unnecessary `extends` that limit reusability
  • Forgetting that generic type parameters are erased at runtime
  • Not leveraging type inference — explicitly passing type args when TS can infer them
  • Creating overly complex generic signatures that are hard to read and maintain

Interview Tips

  • Be able to write a generic function from scratch during an interview
  • Explain the difference between generics and `any` in terms of type safety
  • Know how `keyof` and `extends` work together for property-safe access
  • Mention real-world use cases: API responses, collections, utility functions

Related Concepts