Lesson 12 of 20

Generics

Generic Functions

Generics let you write reusable code that works with multiple types while maintaining type safety. Think of them as type variables.

Example
// Without generics — loses type info
function identity(value: any): any {
  return value;
}

// With generics — preserves type
function identity<T>(value: T): T {
  return value;
}

let str = identity<string>("hello"); // type: string
let num = identity(42);              // type: number (inferred)

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

getLength("hello");    // 5
getLength([1, 2, 3]);  // 3
getLength(123);        // Error: number has no length

Generic Interfaces & Types

You can use generics with interfaces and type aliases to create reusable data structures.

Example
// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

type UserResponse = ApiResponse<{ name: string; email: string }>;
type ProductResponse = ApiResponse<{ id: number; price: number }>;

// Generic type with multiple parameters
type Pair<K, V> = {
  key: K;
  value: V;
};

const entry: Pair<string, number> = { key: "age", value: 25 };

Generic Constraints

Use extends to constrain what types a generic can accept.

Example
// Constrain to objects with an id
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

const users = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" }
];

const found = findById(users, 1); // type preserved

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 25 };
getProperty(user, "name"); // string
getProperty(user, "foo");  // Error: not a key of user