Generic Classes
Parameterize a class by type so one implementation serves every element type safely.
Classes take type parameters just like functions do. The parameter is in scope across the whole class body — fields, methods, return types — so a single implementation stays precise for whatever type a caller supplies.
class Box<T> {
constructor(private value: T) {}
get(): T { return this.value; }
map<U>(fn: (v: T) => U): Box<U> {
return new Box(fn(this.value));
}
}Note map introduces its own parameter U and returns a differently-typed Box — method-level generics compose with the class-level one. You can also constrain the class parameter (class Repo<T extends { id: string }>) to require an id on every stored item.
This is exactly how typed collections, result wrappers, and repositories are written in real codebases.
Example
When to use it
- A typed EventEmitter<EventMap> class is generic so each event name is tied to its specific payload type.
- A Repository<T, ID> class is generic so the same CRUD implementation works for any entity type.
- A typed cache class Cache<K, V> parameterises both the key and value types for complete type safety.
More examples
Generic class basics
A generic Box class holds a single value whose type is fixed at construction, preventing type mismatch on set().
class Box<T> {
private value: T;
constructor(initial: T) { this.value = initial; }
get(): T { return this.value; }
set(v: T): void { this.value = v; }
}
const numBox = new Box(42);
const strBox = new Box('hello');
numBox.set(100); // OK
numBox.set('oops'); // Error: Argument of type 'string' is not assignableGeneric class with constraint
Constrains T to comparable types so the sort call is safe, and the class can be reused for both numbers and strings.
class SortedList<T extends number | string> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
this.items.sort();
}
first(): T | undefined { return this.items[0]; }
}
const list = new SortedList<number>();
list.add(5); list.add(2); list.add(8);
console.log(list.first()); // 2Two type parameters
Uses two type parameters K and V so key and value types are independently typed and constrained.
class Dictionary<K extends string, V> {
private store: Record<string, V> = {};
set(key: K, value: V): void { this.store[key] = value; }
get(key: K): V | undefined { return this.store[key]; }
has(key: K): boolean { return key in this.store; }
}
const d = new Dictionary<string, number>();
d.set('age', 30);
console.log(d.get('age')); // 30
Discussion