Q: What design patterns come up frequently in JavaScript? Give examples for module, singleton, observer, factory, and strategy.

Answer:

Classic GoF patterns translate to JS, but most look quite different because the language has first-class functions, prototypes, dynamic typing, and modules. Idiomatic JS leans on closures, higher-order functions, and dependency injection more than on the explicit class hierarchies of Java or C++.

1. Module Pattern

Encapsulate private state behind a public API. Pre-ESM idiom uses IIFE + closures; today ES modules give it natively.

// IIFE version (legacy)
const Counter = (function () {
  let count = 0;
  return {
    inc() { count++; },
    get() { return count; },
  };
})();

// ESM version — the file itself is the module
let count = 0;
export const inc = () => count++;
export const get = () => count;

Each importer sees the same module instance (modules are singletons at the loader level).

2. Singleton

A single instance shared across the program. In JS, an ES module already behaves like a singleton, so writing an explicit Singleton class is often unnecessary.

// db.js
let instance;
export function getDb() {
  if (!instance) instance = createConnection();
  return instance;
}

[!NOTE] Be cautious — global singletons make testing harder, hide dependencies, and break in environments that spin multiple module realms (SSR, workers). Prefer dependency injection where you can.

3. Observer (Pub/Sub, EventEmitter)

A producer notifies many subscribers without knowing who they are. Foundation of DOM events, Node's EventEmitter, and reactive libraries.

class Emitter {
  constructor() { this.listeners = new Map(); }
  on(event, fn) {
    const set = this.listeners.get(event) ?? new Set();
    set.add(fn);
    this.listeners.set(event, set);
    return () => set.delete(fn); // unsubscribe handle
  }
  emit(event, payload) {
    this.listeners.get(event)?.forEach(fn => fn(payload));
  }
}

Returning the unsubscribe function is the standard ergonomic touch; it removes the need for a separate off.

4. Factory

A function that returns objects, hiding which concrete implementation is chosen. Reduces new coupling and enables polymorphism.

function makeLogger(env) {
  if (env === 'prod')  return { log: msg => sendToCloud(msg) };
  if (env === 'test')  return { log: () => {} };
  return                       { log: msg => console.log(msg) };
}

const log = makeLogger(process.env.NODE_ENV);
log.log('hello');

Factories pair beautifully with closures — the returned object carries private state.

5. Strategy

Encapsulate interchangeable algorithms behind a common interface. Trivial in JS because functions are values.

const strategies = {
  fifo: q => q.shift(),
  lifo: q => q.pop(),
  priority: q => q.sort((a, b) => a.p - b.p).shift(),
};

function process(queue, strategyName) {
  const next = strategies[strategyName] ?? strategies.fifo;
  while (queue.length) handle(next(queue));
}

Other Patterns Worth Knowing

Decorator (function form):

const withTiming = fn => async (...args) => {
  const t = performance.now();
  try { return await fn(...args); }
  finally { console.log(fn.name, performance.now() - t, 'ms'); }
};
const timedFetch = withTiming(fetchUser);

Adapter / Facade: wrap a verbose API in a simpler one.

const storage = {
  get: key => JSON.parse(localStorage.getItem(key) ?? 'null'),
  set: (key, v) => localStorage.setItem(key, JSON.stringify(v)),
};

Command: package an action as data — undo/redo systems, queue workers.

const command = { type: 'MOVE', from: [0, 0], to: [3, 4] };
dispatch(command);

Iterator: the language gives you it for free via Symbol.iterator and generators (see Generators chapter).

Anti-Patterns to Avoid

  • God objects — single 5000-line module that everyone imports.
  • Stringly typed Strategydo('fifo') with no type safety. In TS, use a union literal.
  • Subclass abuse — JS classes encourage tall hierarchies. Prefer composition (function compose(...fs)).
  • Singleton + global mutable state — turns testing into fixture archaeology.

Why JS Has Fewer "Pattern Books"

Because functions are first-class and modules already provide encapsulation, many GoF patterns degenerate to "just write a function" or "use closure". The patterns above still apply; the rest are often over-engineering in idiomatic JavaScript.