Design patterns are reusable solutions to commonly occurring problems in software design. They fall into 3 categories:
🏗️ Creational Patterns
How objects are created
- 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
Use when: Managing a shared resource like a DB connection or config object.
- 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");
Use when: You need to create different object types based on a condition.
- 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();
Use when: Building complex objects like SQL queries, form configs, or request options.
🔗 Structural Patterns
How objects are composed/organized
- 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
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
Use when: Adding features like logging, caching, or auth checks to functions.
🔔 Behavioral Patterns
How objects communicate
- 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
Use when: Building event systems, real-time updates, or state management.
- 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);
}
Use when: You need to switch between different implementations of the same behavior (sorting, payment methods, validation rules).
Quick Reference

Top comments (0)