Explorer
JavaScript

Design Patterns in JavaScript

JavaScript Theory & Concepts

Design Patterns in JavaScript

Classic creational, structural, and behavioral patterns tailored for modern idiomatic JavaScript.

📖 The Story & Real-World Analogy

The Master Architect Catalog

"When architects build a modern city, they do not invent a new shape for every door hinge, elevator pulley, or water pipeline from scratch. They pull proven architectural templates from a master design catalog: • **Singleton**: The City Central Bank vault (exactly ONE instance can ever exist). • **Observer / Pub-Sub**: The Town Crier ringing the town bell (when an event happens, all subscribed citizens hear the news without the crier needing to know each citizen personally). • **Factory**: The Automated Vehicle Plant (you ask for an "electric car", and the plant instantiates the correct model without exposing complex manufacturing internals)."

Design patterns provide battle-tested solutions to recurring software architecture challenges. In JavaScript, patterns emphasize composition, closures, and first-class functions over rigid class inheritance.

⚙️ How It Works Under The Hood (Step-by-Step)
1

Creational: Singleton & Factory

Singleton ensures a single shared instance (ES modules are natural singletons). Factory abstracts object instantiation logic based on input parameters.

2

Structural: Adapter, Decorator, and Proxy

Adapter maps incompatible interfaces. Decorator wraps objects to add behaviors dynamically. Proxy intercepts operations for validation or caching.

3

Behavioral: Observer / Pub-Sub

Establishes a one-to-many dependency where publisher emits events and multiple subscribers react without tight coupling.

4

Behavioral: Strategy Pattern

Encapsulates interchangeable algorithms (e.g., payment strategies: CreditCard, PayPal, Crypto) behind a unified execution interface.

💻 Interactive Code Walkthrough

Implementing a production-grade Pub-Sub (EventEmitter) pattern:

JAVASCRIPT
class EventEmitter {
  constructor() {
    this.events = new Map();
  }

  subscribe(eventName, callback) {
    if (!this.events.has(eventName)) {
      this.events.set(eventName, new Set());
    }
    const handlers = this.events.get(eventName);
    handlers.add(callback);

    // Return an unsubscribe function
    return () => handlers.delete(callback);
  }

  emit(eventName, ...args) {
    const handlers = this.events.get(eventName);
    if (handlers) {
      handlers.forEach(fn => fn(...args));
    }
  }
}

// Practical decoupled usage
const bus = new EventEmitter();

const unsubscribe = bus.subscribe("ORDER_PLACED", (order) => {
  console.log(`[Email Service] Sending receipt to: ${order.email}`);
});

bus.subscribe("ORDER_PLACED", (order) => {
  console.log(`[Analytics] Recording sale of $${order.total}`);
});

bus.emit("ORDER_PLACED", { email: "dev@consistcode.com", total: 99 });
Console Output:
CODE
[Email Service] Sending receipt to: dev@consistcode.com
[Analytics] Recording sale of $99
⚠️ Common Pitfalls & Interview Traps
Trap
Over-Engineering with Rigid Patterns

The Risk: Applying complex OOP patterns (like Abstract Factory or Visitor) where a simple pure function or object literal would suffice.

The Fix: Keep patterns pragmatic. JavaScript's first-class functions often replace entire OOP pattern hierarchies with one-liners.

⚡ 30-Second Quick Revision Cheat Sheet (TL;DR)
  • ✓ Pub-Sub / Observer decouples event producers from consumers.
  • ✓ Singleton: Exactly one instance per runtime (ES modules are singletons by default).
  • ✓ Factory: Encapsulates instantiation complexity behind a clean function.
  • ✓ Strategy: Passes interchangeable algorithmic functions as arguments.
  • ✓ Favor functional composition over heavy classical inheritance.

Finished this lesson?

Mark this chapter complete to update your learning streak and unlock the next lesson.