Anonymous View

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)