Topics in this subject
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):

  1. Implement the module pattern and explain the privacy it gives.
  2. Observer vs Pub-Sub — subtle difference.
  3. Where have you used the strategy pattern to avoid if/else sprawl?