JavaScript
2 min read
Updated 4 Aug 2026
Part 23 — 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 questions (Part 23):
- Implement the module pattern and explain the privacy it gives.
- Observer vs Pub-Sub — subtle difference.
- Where have you used the strategy pattern to avoid
if/elsesprawl?