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.

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