Essential JavaScript Design Patterns
Learn the most useful design patterns in JavaScript: Singleton, Observer, Factory, Module, and more with practical examples.
Why Design Patterns?
Design patterns are proven solutions to common programming problems. They help you write maintainable, scalable code.
1. Singleton Pattern
Ensures only one instance of a class exists.
``javascript
class Database {
static #instance;
constructor(connectionString) { if (Database.#instance) { return Database.#instance; } this.connection = connectionString; Database.#instance = this; }
static getInstance() { return Database.#instance; }
query(sql) {
console.log(\Executing: \${sql}\);
}
}
const db1 = new Database('postgres://localhost/mydb');
const db2 = new Database('mysql://localhost/other');
console.log(db1 === db2); // true (same instance)
`
2. Observer Pattern
Define a subscription mechanism to notify multiple objects about events.
`javascript
class EventEmitter {
#listeners = new Map();
on(event, callback) { if (!this.#listeners.has(event)) { this.#listeners.set(event, new Set()); } this.#listeners.get(event).add(callback); return () => this.off(event, callback); // unsubscribe }
off(event, callback) { this.#listeners.get(event)?.delete(callback); }
emit(event, ...args) { this.#listeners.get(event)?.forEach(cb => cb(...args)); } }
const bus = new EventEmitter();
const unsub = bus.on('userLogin', user => console.log(\Welcome \${user}\));
bus.emit('userLogin', 'Alice'); // "Welcome Alice"
unsub(); // Unsubscribe
`
3. Factory Pattern
Create objects without specifying the exact class.
`javascript
function createNotification(type, message) {
const base = { message, timestamp: Date.now() };
switch (type) {
case 'email':
return { ...base, type: 'email', send() { console.log('Email:', message); } };
case 'sms':
return { ...base, type: 'sms', send() { console.log('SMS:', message); } };
case 'push':
return { ...base, type: 'push', send() { console.log('Push:', message); } };
default:
throw new Error('Unknown notification type');
}
}
const notification = createNotification('email', 'Hello!');
notification.send(); // "Email: Hello!"
`
4. Strategy Pattern
Define a family of algorithms, making them interchangeable.
`javascript
const strategies = {
bubble: (arr) => { /* bubble sort */ return [...arr].sort((a, b) => a - b); },
quick: (arr) => { /* quick sort */ return [...arr].sort((a, b) => a - b); },
merge: (arr) => { /* merge sort */ return [...arr].sort((a, b) => a - b); },
};
class Sorter { #strategy;
constructor(strategyName) { this.#strategy = strategies[strategyName]; }
sort(data) { return this.#strategy(data); }
setStrategy(name) { this.#strategy = strategies[name]; } }
const sorter = new Sorter('quick');
console.log(sorter.sort([3, 1, 4, 1, 5]));
`
5. Proxy Pattern
`javascript
const createCachedApi = (fetcher) => {
const cache = new Map();
return new Proxy(fetcher, {
apply(target, thisArg, args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('Cache hit');
return cache.get(key);
}
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
return result;
}
});
};
``
Summary
Master these patterns to write cleaner JavaScript. Use Singleton for shared resources, Observer for event-driven code, Factory for object creation, Strategy for swappable algorithms, and Proxy for access control and caching.