On this page
Design Patterns in JavaScript
Design patterns are reusable solutions to common software design problems. JavaScript’s flexibility supports many classic patterns.
Module Pattern
Encapsulate private state with a public API:
const Counter = (function() {
let count = 0;
return {
increment() { return ++count; },
decrement() { return --count; },
getCount() { return count; }
};
})();
Counter.increment(); // 1
Counter.getCount(); // 1
// count is not accessible from outside
ES modules provide native encapsulation:
// counter.js
let count = 0;
export function increment() { return ++count; }
export function getCount() { return count; }
Singleton Pattern
Ensure only one instance exists:
class Database {
static #instance = null;
constructor() {
if (Database.#instance) {
return Database.#instance;
}
this.connection = 'connected';
Database.#instance = this;
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Factory Pattern
Create objects without specifying exact class:
function createUser(type, name) {
const users = {
admin: () => ({ name, role: 'admin', permissions: ['read', 'write', 'delete'] }),
guest: () => ({ name, role: 'guest', permissions: ['read'] })
};
return users[type]?.() ?? users.guest();
}
console.log(createUser('admin', 'Alice'));
Observer Pattern
Subscribe to and notify on state changes:
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, callback) {
(this.listeners[event] ??= []).push(callback);
}
emit(event, data) {
(this.listeners[event] || []).forEach(cb => cb(data));
}
off(event, callback) {
this.listeners[event] = (this.listeners[event] || [])
.filter(cb => cb !== callback);
}
}
const emitter = new EventEmitter();
emitter.on('data', (d) => console.log('Received:', d));
emitter.emit('data', { id: 1 }); // Received: { id: 1 }
Strategy Pattern
Swap algorithms at runtime:
const strategies = {
creditCard: (amount) => `Paid $${amount} with credit card`,
paypal: (amount) => `Paid $${amount} with PayPal`,
crypto: (amount) => `Paid $${amount} with crypto`
};
function checkout(method, amount) {
return strategies[method]?.(amount) ?? 'Unknown payment method';
}
console.log(checkout('paypal', 99));
Decorator Pattern
Add behavior without modifying original object:
function withLogging(fn) {
return function(...args) {
console.log(`Calling with`, args);
const result = fn(...args);
console.log(`Result:`, result);
return result;
};
}
function add(a, b) { return a + b; }
const loggedAdd = withLogging(add);
loggedAdd(2, 3); // logs args and result
MVC / MVVM (Conceptual)
Common in front-end frameworks:
- Model — data and business logic
- View — UI presentation
- Controller/ViewModel — connects model and view
React approximates this with components + state; Angular uses full MVC/MVVM.
When to Use Patterns
| Pattern | Use when |
|---|---|
| Module | Hide implementation details |
| Singleton | One shared resource (config, connection pool) |
| Factory | Object type depends on input |
| Observer | Event-driven updates (UI, pub/sub) |
| Strategy | Multiple interchangeable algorithms |
| Decorator | Extend behavior transparently |
Use patterns to solve real problems — avoid over-engineering simple code.