Common JavaScript Design Patterns
Design patterns are reusable solutions to common programming problems. Understanding these patterns helps write more maintainable and scalable JavaScript applications.
Singleton Pattern
// Classic Singleton
const Database = (function() {
let instance;
function createInstance() {
return {
data: [],
add(item) { this.data.push(item); },
remove(item) { /* ... */ }
};
}
return {
getInstance() {
if (!instance) instance = createInstance();
return instance;
}
};
})();
// ES6 Singleton
class Settings {
static instance;
constructor() {
if (Settings.instance) return Settings.instance;
Settings.instance = this;
}
}Observer Pattern
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
}
// Usage
const emitter = new EventEmitter();
emitter.on('userUpdated', user => console.log(user));
emitter.emit('userUpdated', { name: 'John' });Factory Pattern
// Simple Factory
class UserFactory {
createUser(type) {
switch(type) {
case 'admin':
return new AdminUser();
case 'regular':
return new RegularUser();
default:
throw new Error('Invalid user type');
}
}
}
// Abstract Factory
class UIFactory {
createButton() { /* ... */ }
createInput() { /* ... */ }
}
class DarkThemeFactory extends UIFactory {
createButton() { return new DarkButton(); }
createInput() { return new DarkInput(); }
}Decorator Pattern
// Class Decorator
function readonly(target, key, descriptor) {
descriptor.writable = false;
return descriptor;
}
class Example {
@readonly
pi() { return 3.14; }
}
// Function Decorators
function log(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with `, args);
return original.apply(this, args);
};
return descriptor;
}
class Math {
@log
add(a, b) { return a + b; }
}Module Pattern
// Revealing Module Pattern
const ShoppingCart = (function() {
// Private variables
let items = [];
// Private methods
function calculateTotal() {
return items.reduce((total, item) => total + item.price, 0);
}
// Public API
return {
addItem(item) {
items.push(item);
},
getTotal() {
return calculateTotal();
}
};
})();Common Interview Follow-up Questions
- When would you use a Singleton vs Module pattern?
- How do you implement the Observer pattern in React?
- What are the trade-offs of using the Factory pattern?
- How do decorators improve code reusability?
Best Practices
- Choose patterns based on specific needs
- Don't over-engineer solutions
- Consider maintainability and testability
- Document pattern usage in your codebase
- Be consistent with pattern implementation