Design Patterns
Revision notes.
| Pattern | Problem it solves | JS example |
|---|---|---|
| Module | encapsulate private state | IIFE/closure or ES module |
| Singleton | one shared instance | export a single object/instance |
| Factory | create objects without new everywhere |
function returning objects |
| Observer / Pub-Sub | notify many on change | EventEmitter, event bus |
| Strategy | swap algorithms at runtime | pass functions/objects |
| Decorator | add behaviour dynamically | wrapper functions/HOFs |
| Adapter | make incompatible APIs fit | wrapper translating calls |
| Facade | simple API over complex subsystem | a service class |
| Builder | construct complex objects step by step | chained methods returning this |
| MVC/MVVM | separate data/view/logic | frameworks |
| Dependency Injection | decouple, testability | pass deps into constructors/functions |
// Module (closure) — private + public API
const Counter = (() => { let n = 0; return { inc: () => ++n, get: () => n }; })();
// Singleton
class Config { static #instance; static get() { return Config.#instance ??= new Config(); } }
// Observer / Pub-Sub
class EventBus {
#listeners = new Map();
on(evt, fn) { (this.#listeners.get(evt) ?? this.#listeners.set(evt, []).get(evt)).push(fn); return this; }
emit(evt, data) { (this.#listeners.get(evt) ?? []).forEach(fn => fn(data)); }
}
// Strategy
const strategies = { asc:(a,b)=>a-b, desc:(a,b)=>b-a };
const sortBy = (arr, how) => [...arr].sort(strategies[how]);
// Builder
class QueryBuilder {
#parts = [];
select(c){ this.#parts.push(`SELECT ${c}`); return this; }
from(t){ this.#parts.push(`FROM ${t}`); return this; }
build(){ return this.#parts.join(" "); }
}
new QueryBuilder().select("*").from("users").build();
Interview Q&A#
Q1. Implement the module pattern and explain the privacy it gives. An IIFE (or ES module) closes over local variables and returns only a public API; the inner state is unreachable from outside because nothing exposes a reference to it.
const Counter = (() => { let n = 0; return { inc: () => ++n, get: () => n }; })();
Here n is truly private — only inc/get can touch it.
Q2. Observer vs Pub-Sub — the subtle difference. In Observer, subjects hold direct references to their observers and notify them directly (tight coupling). In Pub-Sub, publishers and subscribers don't know each other — an event bus/broker sits in between, so they're fully decoupled.
Q3. Where would you use the strategy pattern to avoid if/else sprawl?
Any time behaviour varies by a key you can look up instead of branching — e.g. sort comparators, pricing/discount rules, or payment providers. Store strategies in a map and select at runtime:
const strategies = { asc: (a,b)=>a-b, desc: (a,b)=>b-a };
const sortBy = (arr, how) => [...arr].sort(strategies[how]);