Design Patterns in JavaScript

Imagine you are building a house. Before laying a single brick, you follow a blueprint. The blueprint is not the house — it is a proven plan that tells you how to build it correctly. Design patterns work the same way in code.

A design pattern is a reusable solution to a common problem in software development. It is not a specific piece of code you copy and paste. It is more like a template or strategy — a way of thinking about how to solve a problem that developers have faced many times before.

Design patterns were popularised by the famous "Gang of Four" book from the 1990s. They identified 23 patterns grouped into three categories:

  • Creational Patterns — about how objects are created
  • Structural Patterns — about how objects are organised and composed
  • Behavioral Patterns — about how objects communicate with each other

You do not need to memorise all 23. In JavaScript, a handful of them come up again and again — in frameworks, libraries, and real-world projects. Understanding even a few will make your code cleaner, easier to read, and easier to maintain.

Good to know: Many JavaScript frameworks like React, Angular, and Node.js are built using design patterns under the hood. Learning them helps you understand why those tools are designed the way they are.

Creational patterns are all about how you create objects. Instead of using new SomeClass() everywhere, creational patterns give you smarter, more flexible ways to build things.

Factory Function Pattern

A factory function is just a regular function that creates and returns an object. Think of it like an actual factory — you give it some inputs and it gives back a finished product.

Why use it? Because it hides the details of how an object is created. The code that uses the factory does not need to care about how it was built.

javascript

// Factory function – creates a user object
function createUser(name, role) {
  return {
    name: name,
    role: role,
    greet() {
      console.log(`Hi, I am ${this.name} and I am a ${this.role}.`);
    }
  };
}

const alice = createUser("Alice", "Admin");
const bob   = createUser("Bob",   "Editor");

alice.greet(); // Hi, I am Alice and I am a Admin.
bob.greet();   // Hi, I am Bob and I am a Editor.

You can call createUser() as many times as you want. Each call returns a fresh, independent object. No new keyword needed, no class required.

Constructor Pattern

Before ES6 classes existed, JavaScript developers used constructor functions to build objects. A constructor function is a regular function, but by convention it starts with a capital letter. You call it with the new keyword.

javascript

// Constructor function
function Car(brand, model) {
  this.brand = brand;
  this.model = model;
  this.describe = function() {
    console.log(`${this.brand} ${this.model}`);
  };
}

const car1 = new Car("Toyota", "Camry");
const car2 = new Car("Honda",  "Civic");

car1.describe(); // Toyota Camry
car2.describe(); // Honda Civic

When you use new, JavaScript automatically creates a new empty object, sets this to that object, and returns it. Today, most developers use ES6 class syntax instead — but understanding constructor functions helps you read older code.

Singleton Pattern

A Singleton ensures that only one instance of something exists in your entire application. It is like the CEO of a company — there is only one, and everyone talks to the same person.

Common use cases: a configuration manager, a database connection, a logging service.

javascript

// Singleton – only one instance ever exists
const AppConfig = (function() {
  let instance;

  function createInstance() {
    return {
      theme: "light",
      language: "en",
      version: "1.0.0"
    };
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

const config1 = AppConfig.getInstance();
const config2 = AppConfig.getInstance();

config1.theme = "dark";

console.log(config2.theme); // "dark" — same object!
console.log(config1 === config2); // true — same instance

Both config1 and config2 point to the exact same object in memory. Changing one changes the other — because they are the same instance. This is exactly the point of the Singleton pattern.

Structural patterns are about how you organise and compose your code. They help you keep things tidy, hide complexity, and make different parts of your code work together without getting tangled.

Module Pattern

The Module pattern lets you group related code together and keep some things private. Think of it like an office building. People inside can see and use everything. Visitors only get access to the reception area.

Before ES6 modules existed, this pattern was the primary way to organise JavaScript code without polluting the global scope.

javascript

// Module Pattern using IIFE (Immediately Invoked Function Expression)
const Counter = (function() {
  // Private – not accessible from outside
  let count = 0;

  // Public – returned and accessible from outside
  return {
    increment() {
      count++;
    },
    decrement() {
      count--;
    },
    getCount() {
      return count;
    }
  };
})();

Counter.increment();
Counter.increment();
Counter.increment();
Counter.decrement();

console.log(Counter.getCount()); // 2
console.log(Counter.count);      // undefined – count is private!

The variable count is hidden inside the function. The outside world cannot touch it directly. You can only interact with it through the public methods (increment, decrement, getCount). This is called encapsulation.

Decorator Pattern

The Decorator pattern lets you add new behaviour to an existing object without changing it. Think of it like buying a phone case — you keep your original phone untouched, but add new features on top.

javascript

// Base coffee object
function Coffee() {
  this.cost = function() { return 5; };
  this.description = function() { return "Coffee"; };
}

// Decorator: adds milk
function withMilk(coffee) {
  const cost        = coffee.cost.bind(coffee);
  const description = coffee.description.bind(coffee);

  coffee.cost        = function() { return cost() + 1; };
  coffee.description = function() { return description() + ", Milk"; };

  return coffee;
}

// Decorator: adds sugar
function withSugar(coffee) {
  const cost        = coffee.cost.bind(coffee);
  const description = coffee.description.bind(coffee);

  coffee.cost        = function() { return cost() + 0.5; };
  coffee.description = function() { return description() + ", Sugar"; };

  return coffee;
}

let myCoffee = new Coffee();
myCoffee = withMilk(myCoffee);
myCoffee = withSugar(myCoffee);

console.log(myCoffee.description()); // Coffee, Milk, Sugar
console.log(myCoffee.cost());        // 6.5

You start with a plain coffee and keep wrapping it with decorators. Each decorator adds new behaviour without modifying the original object. You can stack as many decorators as you need.

Facade Pattern

The Facade pattern provides a simple interface to a complex system. Think of a TV remote. You press one button and the TV turns on, the volume adjusts, and the last channel loads. You do not see all the internal logic — just a simple button.

javascript

// Complex subsystems
const CPU     = { start()    { console.log("CPU started");     } };
const Memory  = { load()     { console.log("Memory loaded");   } };
const Storage = { readData() { console.log("Storage read");    } };

// Facade – simple interface that hides all the complexity
const Computer = {
  start() {
    CPU.start();
    Memory.load();
    Storage.readData();
    console.log("Computer is ready!");
  }
};

// You only need to know one thing:
Computer.start();
// CPU started
// Memory loaded
// Storage read
// Computer is ready!

The caller just uses Computer.start(). They do not need to know about CPU, Memory, or Storage. The Facade hides all of that complexity behind one clean entry point. This is one of the most widely used patterns in real projects.

Behavioral patterns are about how objects talk to each other. When one part of your code needs to communicate with another, behavioral patterns give you clean, maintainable ways to do it.

Observer Pattern

The Observer pattern is like a newsletter subscription. You subscribe to a newsletter. When the author publishes something new, you automatically get notified — along with everyone else who subscribed. You do not need to keep checking.

In code: one object (the subject or publisher) maintains a list of observers. When something changes, it notifies all of them automatically. This is the core idea behind DOM events, React's state updates, and many libraries.

javascript

// Simple Observer / EventEmitter
class EventEmitter {
  constructor() {
    this.listeners = {};
  }

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

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

const emitter = new EventEmitter();

// Subscribe – two observers
emitter.on("login", (user) => console.log(`Welcome, ${user}!`));
emitter.on("login", (user) => console.log(`Logging access for: ${user}`));

// Publish – both observers are notified automatically
emitter.emit("login", "Alice");
// Welcome, Alice!
// Logging access for: Alice

The emitter does not need to know anything about its observers. It just fires the event and moves on. This loose connection between parts of your code makes your system flexible and easy to extend.

Strategy Pattern

The Strategy pattern lets you swap out the logic (algorithm) used by a function at runtime. Think of choosing a route on Google Maps — you pick "Fastest", "No tolls", or "Scenic". The destination is the same, but the strategy for getting there changes.

javascript

// Different sort strategies
const bubbleSort = (arr) => {
  const a = [...arr];
  for (let i = 0; i < a.length; i++)
    for (let j = 0; j < a.length - i - 1; j++)
      if (a[j] > a[j+1]) [a[j], a[j+1]] = [a[j+1], a[j]];
  return a;
};

const nativeSort = (arr) => [...arr].sort((a, b) => a - b);

// Sorter uses whichever strategy you pass in
function Sorter(strategy) {
  this.sort = function(data) {
    return strategy(data);
  };
}

const numbers = [5, 1, 8, 2, 9, 3];

const sorter1 = new Sorter(bubbleSort);
console.log(sorter1.sort(numbers)); // [1, 2, 3, 5, 8, 9]

const sorter2 = new Sorter(nativeSort);
console.log(sorter2.sort(numbers)); // [1, 2, 3, 5, 8, 9]

The Sorter does not care which sorting algorithm you use. You pass in the strategy and it does the work. This makes it easy to swap algorithms without touching the code that calls the sorter.

Command Pattern

The Command pattern turns actions into objects. Instead of calling a function directly, you wrap the call inside a command object. This makes it easy to queue, undo, or replay actions.

Think of a restaurant. You do not walk into the kitchen and cook your own food. You write your order on a ticket (the command) and give it to the waiter. The kitchen processes orders from the ticket — and if something is wrong, the ticket can be cancelled.

javascript

// Command objects
const light = { on: false };

const turnOnCommand  = { execute: () => { light.on = true;  console.log("Light ON");  } };
const turnOffCommand = { execute: () => { light.on = false; console.log("Light OFF"); } };

// Invoker – processes the command queue
class RemoteControl {
  constructor() { this.history = []; }

  press(command) {
    command.execute();
    this.history.push(command);
  }
}

const remote = new RemoteControl();
remote.press(turnOnCommand);   // Light ON
remote.press(turnOffCommand);  // Light OFF
remote.press(turnOnCommand);   // Light ON

console.log("Commands run:", remote.history.length); // 3

Each command is a self-contained object with an execute() method. The remote control just calls execute() — it does not know or care what the command actually does. The history array lets you replay or undo commands if you need to.

  • Design patterns are proven, reusable solutions to common problems. They are not code — they are templates for thinking about code.
  • Creational patterns control how objects are made. Factory Functions hide construction details. Constructor Pattern uses new to build objects. Singleton ensures only one instance ever exists.
  • Structural patterns organise how code is put together. Module Pattern keeps things private and grouped. Decorator adds behaviour without changing the original. Facade simplifies complex systems with a clean interface.
  • Behavioral patterns define how parts of your code communicate. Observer notifies many subscribers when something changes. Strategy swaps algorithms at runtime. Command turns actions into objects for queuing or undoing.
  • You do not need to use all 23 patterns. Even knowing 5 or 6 will make you a noticeably better developer.
  • Most modern frameworks (React, Angular, Vue, Node.js) are built on top of these patterns. Understanding them helps you understand the tools you use every day.
What's next? Check out Performance Optimization to learn how to make your JavaScript faster, or explore other advanced topics in the sidebar.
  • What is a design pattern and why are they useful?
  • What are the three main categories of design patterns?
  • What is the Factory Function pattern and when would you use it?
  • What is the difference between a Factory Function and a Constructor Function?
  • What is the Singleton pattern? Give a real-world use case.
  • What is the Module pattern? How does it create private variables?
  • Explain the Decorator pattern with an example.
  • What is the Facade pattern and why is it useful?
  • How does the Observer pattern work? What problem does it solve?
  • What is the difference between the Observer pattern and direct function calls?
  • What is the Strategy pattern? When would you use it over an if/else block?
  • What is the Command pattern? What advantage does it give over calling functions directly?
  • Can you name any design patterns used in JavaScript frameworks like React or Node.js?