100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TypeScript

Generic Classes in TypeScript

Learn how to parametrize class instance and method types with generics to build reusable, type-safe data structures in TypeScript.

GenericsIntermediate9 min readJul 8, 2026
Analogies

1. Introduction

Just like functions and interfaces, classes can be generic. A generic class declares one or more type parameters after the class name, and those parameters can then be used to type properties, constructor parameters, and methods throughout the class. This is how TypeScript's own built-in structures like Map<K, V>, Set<T>, and Array<T> are able to work with any element type while staying fully type-checked.

🏏

Cricket analogy: A team database structured as Map<PlayerName, BattingAverage> can hold Virat Kohli's average or Steve Smith's average with the same generic Map<K, V> class, fully type-checked no matter which player it stores.

2. Syntax

typescript
class Box<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

// Generic class with a constraint
class Repository<T extends { id: number }> {
  private items: T[] = [];

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

  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

// Instantiating a generic class
const numberBox = new Box<number>(10);
const stringBox = new Box("hello"); // T inferred from constructor arg

3. Explanation

Generic classes parametrize instance and method types: the type parameter T declared on class Box<T> is available everywhere inside the class body — as the type of properties (private value: T), constructor parameters, and method parameters/return types (getValue(): T). This means one class definition, Box<T>, can safely back Box<number>, Box<string>, or Box<User> instances, each with its own fully checked value type.

🏏

Cricket analogy: A Box<T>-style ScoreEntry<T> class stores private value: T and its getValue(): T returns exactly that type, so ScoreEntry<number> for Kohli's runs and ScoreEntry<string> for Bumrah's dismissal method are each fully checked without duplicating the class.

As with generic functions, TypeScript can often infer the type argument from the constructor call (new Box("hello") infers T as string), but you can also specify it explicitly with new Box<number>(10), which is required when the constructor gives no clue (e.g. an empty generic collection: new Repository<Product>()).

🏏

Cricket analogy: Calling new ScoreEntry("boundary") lets TypeScript infer T as string from the argument, just as declaring new ScoreEntry<number>(4) is fine, but starting an empty new TeamRoster<Player>() with no initial player requires the type argument explicitly.

Generic classes can also use constraints, exactly like generic functions — class Repository<T extends { id: number }> restricts T to only object shapes that have a numeric id, letting findById safely compare item.id. A class can even declare multiple type parameters (class Pair<K, V>) or default type parameters (class Container<T = string>).

🏏

Cricket analogy: A class PlayerRepository<T extends { id: number }> restricts T to any player shape with a numeric id, so findById can safely compare item.id against Virat Kohli's player ID; a class Pair<K, V> could pair a player's name with jersey number.

Gotcha: a generic type parameter declared on a class (like T on Box<T>) cannot be used on static members. Static members belong to the class itself, not to any particular instance, so they have no access to the per-instance type parameter — a static method that needs genericity must declare its own, separate type parameter.

4. Example

typescript
class Box<T> {
  constructor(private value: T) {}

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

interface Product {
  id: number;
  name: string;
}

class Repository<T extends { id: number }> {
  private items: T[] = [];

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

  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }
}

const numberBox = new Box<number>(10);
console.log(numberBox.getValue());
numberBox.setValue(20);
console.log(numberBox.getValue());

const products = new Repository<Product>();
products.add({ id: 1, name: "Laptop" });
products.add({ id: 2, name: "Mouse" });

const found = products.findById(2);
console.log(found?.name);

const missing = products.findById(99);
console.log(missing);

5. Output

text
10
20
Mouse
undefined

6. Key Takeaways

Practice what you learned

Was this page helpful?

Topics covered

#TypeScript#TypeScriptProgrammingStudyNotes#Programming#GenericClassesInTypeScript#Generic#Classes#Syntax#Explanation#OOP#StudyNotes#SkillVeris