Four Pillars — Quick Reference
| Pillar | One-liner | Keyword |
| Encapsulation | Bundle data + methods; hide internals with access modifiers | private / #field |
| Inheritance | Child class reuses parent's fields and methods; "is-a" relationship | extends / super() |
| Polymorphism | Same interface, different implementations; method overriding at runtime | override / virtual |
| Abstraction | Expose what, hide how; define contracts via abstract classes / interfaces | abstract / interface |
Encapsulation
class BankAccount {
#balance = 0 // private field
deposit(n) { if (n > 0) this.#balance += n }
get balance() { return this.#balance } // controlled read
}
// External code can NEVER do: account.#balance = 999
Inheritance & Polymorphism
class Animal {
speak() { return 'generic sound' }
}
class Dog extends Animal {
speak() { return 'woof' } // overrides parent
}
class Cat extends Animal {
speak() { return 'meow' }
}
const animals = [new Dog(), new Cat()]
animals.forEach(a => console.log(a.speak())) // woof, meow
// runtime polymorphism — same .speak() call, different result
Abstraction
// TypeScript abstract class
abstract class Shape {
abstract area(): number // subclasses MUST implement
describe() { return `Area: ${this.area()}` }
}
class Circle extends Shape {
constructor(private r: number) { super() }
area() { return Math.PI * this.r ** 2 }
}
// Interface — pure contract
interface Printable {
print(): void
}
class Report implements Printable {
print() { console.log('Printing report…') }
}
SOLID Principles
| Letter | Principle | Rule of thumb |
| S | Single Responsibility | One class = one reason to change |
| O | Open/Closed | Open for extension, closed for modification |
| L | Liskov Substitution | Subclass must behave correctly wherever parent is used |
| I | Interface Segregation | Many specific interfaces > one fat interface |
| D | Dependency Inversion | Depend on abstractions (interfaces), not concrete classes |
// O — Open/Closed: add new discount types without editing existing code
interface Discount { apply(price: number): number }
class PercentDiscount implements Discount {
constructor(private pct: number) {}
apply(p: number) { return p * (1 - this.pct / 100) }
}
// D — Dependency Inversion
class OrderService {
constructor(private db: Database) {} // Database is an interface
// swap MySQL for Postgres without touching OrderService
}
Design Patterns — Quick Reference
| Pattern | Category | Intent | When to use |
| Singleton | Creational | Exactly one instance | Config, logger, DB connection pool |
| Factory | Creational | Create objects without specifying exact class | Plugin systems, notification types |
| Observer | Behavioural | One-to-many event notification | Event systems, UI state, pub/sub |
| Strategy | Behavioural | Swap algorithms at runtime | Sorting, payment processors, compression |
| Decorator | Structural | Add behaviour by wrapping | Logging middleware, caching layers |
| Adapter | Structural | Make incompatible interfaces work together | Integrating third-party libraries |
Singleton & Observer
// Singleton
class Config {
static #instance
static getInstance() {
if (!Config.#instance) Config.#instance = new Config()
return Config.#instance
}
}
// Observer
class EventEmitter {
#handlers = {}
on(event, fn) { (this.#handlers[event] ??= []).push(fn) }
emit(event, data) { (this.#handlers[event] ?? []).forEach(fn => fn(data)) }
}
Composition vs Inheritance
// Prefer composition ("has-a") over deep inheritance ("is-a")
// BAD deep chain: Vehicle → Car → ElectricCar → AutopilotCar
// GOOD composition:
class Car {
constructor(
private engine: Engine, // composed behaviour
private autopilot: Autopilot
) {}
drive() { this.engine.start(); this.autopilot.navigate() }
}
// Rule: if you need more than 2 levels of inheritance, use composition
Key Rules
- Encapsulate what varies — isolate the parts that change from the parts that stay the same.
- Program to interfaces, not implementations (Dependency Inversion).
- Favour composition over inheritance — avoids fragile base-class problem.
- Tell, don't ask — objects should do things, not expose internal state for callers to act on.
- SOLID violations show up as: classes that are hard to test, code that breaks when you add a feature, copy-pasted logic across subclasses.