Skip to content

Generics

Generics let you write code that works with different types. Think of a box—sometimes you put toys in it, sometimes fruit. The box works the same way regardless of what’s inside.

Without generics, you’d need separate boxes for each type:

interface ToyBox {
  contents: Toy;
}

interface FruitBox {
  contents: Fruit;
}
// This gets repetitive fast...

One box that works with any type. The <T> is a placeholder for whatever type you specify later:

interface Box<T> {
  contents: T;
}

// A box of toys
const toyBox: Box<Toy> = { contents: { name: "Lego" } };

// A box of fruit
const fruitBox: Box<Fruit> = { contents: { name: "Apple" } };

Same idea with functions. Write it once, use it with any type:

function getFirstItem<T>(items: T[]): T {
  return items[0];
}

const firstNumber = getFirstItem([1, 2, 3]); // type: number
const firstWord = getFirstItem(["a", "b", "c"]); // type: string

TypeScript infers the type from what you pass in.

A reusable container class:

class Box<T> {
  private contents: T;

  constructor(item: T) {
    this.contents = item;
  }

  getContents(): T {
    return this.contents;
  }

  replaceContents(newItem: T): void {
    this.contents = newItem;
  }
}

const toyBox = new Box({ name: "Lego", pieces: 500 });
console.log(toyBox.getContents()); // { name: "Lego", pieces: 500 }

const fruitBox = new Box({ name: "Apple", ripe: true });
console.log(fruitBox.getContents()); // { name: "Apple", ripe: true }

Sometimes you need to limit what types are allowed. Use extends to say “only types that have these properties”:

interface HasName {
  name: string;
}

// Only accept items that have a name property
function labelBox<T extends HasName>(item: T): string {
  return `Box contains: ${item.name}`;
}

labelBox({ name: "Lego", pieces: 500 }); // "Box contains: Lego"
labelBox({ name: "Apple" }); // "Box contains: Apple"
labelBox({ color: "red" }); // Error: missing 'name' property

Set a fallback type if none is specified:

interface Box<T = string> {
  contents: T;
}

const stringBox: Box = { contents: "hello" }; // T defaults to string
const numberBox: Box<number> = { contents: 42 }; // T is number