Classes & OOP
Type-safe classes with access modifiers, abstract bases, and inheritance for structured domain models.
Typed Class Members
TypeScript classes support parameter properties in constructors (`constructor(public name: string)`), explicit field declarations, and typed methods. Access modifiers `public`, `private`, and `protected` enforce visibility at compile time — erased in output JS for private/protected (use `#` private fields in JS for runtime privacy).
Implement interfaces on classes to guarantee structural contracts: `class UserService implements IUserRepository`. The class must satisfy all interface members.
- Parameter properties in constructors
- Access modifiers for visibility
- Implement interfaces for contracts
class User {
constructor(
public readonly id: string,
private email: string,
) {}
}Inheritance and super
Subclasses extend base classes with `extends` and call `super()` before accessing `this`. Override methods with compatible signatures — use `override` keyword (with `noImplicitOverride`) to catch accidental shadowing when base methods rename.
Protected members are visible to subclasses but not external code. Use protected for template method patterns where subclasses customize hooks while the base controls flow.
- `override` catches base method renames
- Protected for subclass hooks
- Call `super()` before `this`
class Admin extends User {
override greet() { return `Admin ${super.greet()}`; }
}Abstract Classes
Abstract classes cannot be instantiated directly. They define abstract methods subclasses must implement, plus optional concrete shared logic. Use when you have a base with partial implementation but require subtype-specific behavior.
Prefer interfaces plus composition when you do not need shared implementation. Abstract classes couple subclasses to a single hierarchy — interfaces allow multiple implementation contracts.
- Cannot instantiate abstract classes
- Force subclass method implementation
- Share concrete logic in base
abstract class Repository<T> {
abstract find(id: string): Promise<T | null>;
async exists(id: string) {
return (await this.find(id)) !== null;
}
}Static Members and Singletons
Static methods and properties belong to the class constructor. Use for factory methods, registries, and constants. Avoid singleton abuse — dependency injection containers often provide better testability than global static instances.
When singletons are warranted (config loaders, connection pools), type them explicitly and mock the module in tests rather than fighting static state.
- Static for factories and constants
- Prefer DI over singletons in apps
- Mock static modules in tests
Classes vs Functions in TS Codebases
Modern React and many Node frameworks favor functions and hooks over classes for UI and handlers. Classes remain valuable for domain entities, error hierarchies, SDK clients, and ORM models.
TypeScript makes either pattern safe — choose based on runtime framework and team conventions. Do not force classes where the ecosystem idiomatically uses functions.
- Classes for domain models and SDKs
- Functions for React and route handlers
- Match ecosystem conventions