🌱BeginnerAdvanced Beginner Types

Generics

Generic functions, interfaces, and classes write once, use for any type with full type safety.

Overview

Generics allow you to write code that works with any type while preserving type information. Instead of using `any` (which loses all type safety), you use a **type parameter** `<T>` as a placeholder that TypeScript fills in based on usage. The classic example: a function `identity<T>(value: T): T` that returns what it receives. If you call `identity("hello")`, TypeScript infers `T = string` and knows the return type is `string`. Same function, any type, full safety. Generics work in functions, interfaces, classes, and type aliases. **Constraints** (`extends`) limit which types a type parameter can be: `T extends object` ensures T is an object; `T extends { length: number }` ensures T has a length property.

Key Points

  • •Type parameter: `function fn<T>(arg: T): T` T is resolved at the call site
  • •TypeScript infers T from arguments explicit annotation (`fn<string>(...)`) is often not needed
  • •Generic interface: `interface Box<T> { value: T; }`
  • •Generic class: `class Container<T> { ... }`
  • •Generic type alias: `type Pair<T, U> = { first: T; second: U; }`
  • •Constraint: `T extends Type` T must be assignable to Type
  • •Multiple type params: `function map<T, U>(arr: T[], fn: (x: T) => U): U[]`
  • •Default type param: `interface Container<T = string>` T defaults to string

Examples

Generic functions

// Without generics (loses type info)
function firstAny(arr: any[]): any {
  return arr[0];
}
const x = firstAny([1, 2, 3]);  // type: any TypeScript can't help

// With generics (type-safe)
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

const num  = first([1, 2, 3]);         // type: number | undefined
const str  = first(["a", "b", "c"]);   // type: string | undefined
const none = first<boolean>([]);       // type: boolean | undefined

// Multi-param generic
function zip<T, U>(a: T[], b: U[]): [T, U][] {
  return a.map((item, i) => [item, b[i]]);
}

const pairs = zip([1, 2, 3], ["a", "b", "c"]);
// type: [number, string][]
console.log(pairs);  // [[1,"a"],[2,"b"],[3,"c"]]

Generic interfaces and classes

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

// Generic class
class InMemoryRepo<T extends { id: number }> implements Repository<T> {
  private store: Map<number, T> = new Map();

  findById(id: number): T | undefined {
    return this.store.get(id);
  }

  findAll(): T[] {
    return [...this.store.values()];
  }

  save(item: T): void {
    this.store.set(item.id, item);
  }

  delete(id: number): boolean {
    return this.store.delete(id);
  }
}

type User = { id: number; name: string };
const users = new InMemoryRepo<User>();
users.save({ id: 1, name: "Alice" });
users.save({ id: 2, name: "Bob" });
console.log(users.findAll());  // [{id:1,name:"Alice"}, ...]

Generic constraints

// Constraint: T must have a length property
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(longest("hello", "hi"));       // "hello" (string)
console.log(longest([1,2,3], [4,5]));      // [1,2,3] (array)
// console.log(longest(1, 2));             // Error: number has no .length

// Keyof constraint pick a property by name
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: "Alice", email: "alice@a.com" };
const name = getProperty(user, "name");     // type: string
const id   = getProperty(user, "id");       // type: number
// getProperty(user, "missing");            // Error!

console.log(name, id);

Common Mistakes

  • Using `any` when generics would preserve type safety generics are zero-cost and give you full IDE support
  • Over-constraining type parameters `T extends string` when you just need `T` prevents the function from working on numbers
  • Forgetting to use the type parameter `function fn<T>(x: string): T` T is unconstrained and its return is unsafe; always use T

Note

A good mental model: generics are like function parameters, but for types. Just as `function add(a, b)` accepts any numbers, `function first<T>(arr: T[])` accepts arrays of any type.

Exercises

Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

What is the advantage of generics over using `any`?