Skip to content
All essays
ArchitectureFebruary 13, 202512 min

Design Patterns: Gang of Four (GoF) in Modern JavaScript

Master classic design patterns. Learn GoF patterns and how to apply them in modern JavaScript.

Ü
Ümit Uz
Mobile & Full Stack Developer

Creational Patterns

Singleton

javascript
// Ensure only one instance exists
class Database {
  constructor() {
    if (Database.instance) {
      return Database.instance;
    }
    this.connection = null;
    Database.instance = this;
  }

  connect() { /* ... */ }
}

const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true

Factory

javascript
// Create objects without specifying exact class
class CarFactory {
  create(type) {
    switch (type) {
      case 'sedan':
        return new Sedan();
      case 'suv':
        return new SUV();
      default:
        throw new Error('Unknown car type');
    }
  }
}

const factory = new CarFactory();
const sedan = factory.create('sedan');

Structural Patterns

Adapter

javascript
// Make incompatible interfaces work together
class OldAPI {
  request() { return 'old format'; }
}

class NewAPI {
  fetch() { return 'new format'; }
}

class Adapter {
  constructor(newAPI) {
    this.newAPI = newAPI;
  }

  request() {
    const data = this.newAPI.fetch();
    return `converted: ${data}`;
  }
}

Decorator

javascript
// Add behavior without modifying class
function withLogging(fn) {
  return function(...args) {
    console.log(`Calling ${fn.name} with`, args);
    const result = fn.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
}

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);

loggedAdd(2, 3); // Logs: Calling add with [2, 3], Result: 5

Behavioral Patterns

Observer

javascript
// Subscribe to notifications
class Subject {
  constructor() {
    this.observers = [];
  }

  subscribe(observer) {
    this.observers.push(observer);
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

const subject = new Subject();
subject.subscribe({
  update: (data) => console.log('Received:', data)
});

subject.notify('Hello!');

Strategy

javascript
// Swap algorithms at runtime
class PaymentStrategy {
  pay(amount) { throw new Error('Must implement'); }
}

class CreditCardPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid $${amount} with credit card`);
  }
}

class PayPalPayment extends PaymentStrategy {
  pay(amount) {
    console.log(`Paid $${amount} with PayPal`);
  }
}

class Cart {
  constructor(paymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }

  checkout(amount) {
    this.paymentStrategy.pay(amount);
  }
}

Modern Patterns

Module Pattern

javascript
// Encapsulation with closures
const Module = (() => {
  let privateVar = 0;

  return {
    increment() {
      privateVar++;
    },
    getCount() {
      return privateVar;
    }
  };
})();

Module.increment();
console.log(Module.getCount()); // 1
console.log(Module.privateVar); // undefined

Pub/Sub

javascript
// Event-driven communication
class PubSub {
  constructor() {
    this.events = {};
  }

  subscribe(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }

  publish(event, data) {
    if (this.events[event]) {
      this.events[event].forEach(cb => cb(data));
    }
  }
}

When to Use

Patterns solve common problems but aren't silver bullets:

  1. 1Overuse: Don't force patterns where simple code works
  2. 2Context: Consider team familiarity and project needs
  3. 3Simplicity: Favor simple solutions over clever patterns
  4. 4Pragmatism: Use patterns that actually solve your problem

Learn patterns, then know when to break them.

Related essays

Next essay
AWS vs GCP vs Azure: Cloud Platform Comparison 2025