DEV Community

Cover image for JavaScript Design Patterns
Kiran
Kiran

Posted on

JavaScript Design Patterns

Design patterns are reusable solutions to commonly occurring problems in software design. They fall into 3 categories:

🏗️ Creational Patterns
How objects are created

  1. Singleton Ensures only one instance of a class exists throughout the app.
javascriptclass Database {
  constructor() {
    if (Database.instance) return Database.instance;
    Database.instance = this;
  }
}

const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Enter fullscreen mode Exit fullscreen mode

Use when: Managing a shared resource like a DB connection or config object.

  1. Factory A function/class that creates objects without specifying the exact class.
javascriptfunction createUser(role) {
  if (role === "admin") return { role, permissions: ["read", "write", "delete"] };
  if (role === "guest") return { role, permissions: ["read"] };
}

const admin = createUser("admin");
const guest = createUser("guest");

Enter fullscreen mode Exit fullscreen mode

Use when: You need to create different object types based on a condition.

  1. Builder Constructs complex objects step by step.
javascriptclass QueryBuilder {
  constructor() { this.query = "SELECT "; }
  select(fields) { this.query += fields; return this; }
  from(table) { this.query += ` FROM ${table}`; return this; }
  where(condition) { this.query += ` WHERE ${condition}`; return this; }
  build() { return this.query; }
}

const query = new QueryBuilder()
  .select("*")
  .from("users")
  .where("age > 18")
  .build();
Enter fullscreen mode Exit fullscreen mode

Use when: Building complex objects like SQL queries, form configs, or request options.

🔗 Structural Patterns

How objects are composed/organized

  1. Module Encapsulates code into self-contained units with private and public parts.
javascriptconst CartModule = (() => {
  let items = []; // private

  return {
    add: (item) => items.push(item),
    getItems: () => [...items],
    total: () => items.reduce((sum, i) => sum + i.price, 0),
  };
})();

CartModule.add({ name: "Shoes", price: 50 });
console.log(CartModule.total()); // 50
Enter fullscreen mode Exit fullscreen mode

Use when: You want to avoid polluting the global scope (very common in JS).

5.** Decorator**
Adds new behavior to an object without modifying the original.

javascriptfunction withLogging(fn) {
  return function (...args) {
    console.log(`Calling with`, args);
    return fn(...args);
  };
}

const add = (a, b) => a + b;
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // logs: Calling with [2, 3] → returns 5
Enter fullscreen mode Exit fullscreen mode

Use when: Adding features like logging, caching, or auth checks to functions.

🔔 Behavioral Patterns

How objects communicate

  1. Observer One object (subject) notifies multiple subscribers when something changes.
javascriptclass EventEmitter {
  constructor() { this.listeners = {}; }
  on(event, fn) {
    (this.listeners[event] ??= []).push(fn);
  }
  emit(event, data) {
    this.listeners[event]?.forEach(fn => fn(data));
  }
}

const emitter = new EventEmitter();
emitter.on("login", (user) => console.log(`${user} logged in`));
emitter.emit("login", "Alice"); // Alice logged in
Enter fullscreen mode Exit fullscreen mode

Use when: Building event systems, real-time updates, or state management.

  1. Strategy Defines a family of algorithms and makes them interchangeable at runtime.
javascriptconst sorter = {
  bubble: (arr) => { /* bubble sort logic */ },
  quick:  (arr) => { /* quick sort logic */  },
};

function sortData(data, strategy = "quick") {
  return sorter[strategy](data);
}
Enter fullscreen mode Exit fullscreen mode

Use when: You need to switch between different implementations of the same behavior (sorting, payment methods, validation rules).

Quick Reference

Top comments (0)