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
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 arg3. 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
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
10
20
Mouse
undefined6. Key Takeaways
Practice what you learned
1. In `class Box<T> { constructor(private value: T) {} getValue(): T { return this.value; } }`, what does T parametrize?
2. Why does `new Box("hello")` work without writing `new Box<string>("hello")`?
3. What is wrong with trying to reference the class-level type parameter T inside a `static` method of `class Box<T>`?
4. In `class Repository<T extends { id: number }>`, what is the effect of the constraint?
5. Which of these correctly demonstrates instantiating a generic class with an explicit type argument?
Was this page helpful?
You May Also Like
Generics in TypeScript
Learn how TypeScript generics let you write reusable, type-safe code that works across many types without losing type information.
Generic Constraints in TypeScript
Learn how to restrict generic type parameters with `extends` so generic code can safely rely on specific properties or shapes.
Classes in TypeScript
Learn how TypeScript classes add type-checked properties, methods, and constructor parameter property shorthand on top of ES2015 classes.
Static Members in TypeScript
Define properties and methods that belong to the class itself rather than to individual instances, using the static keyword.
Access Modifiers in TypeScript (public, private, protected)
Control the visibility of class members with public, private, and protected, and understand that these checks exist only at compile time.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics