JavaScript Design Patterns — And When You Should Actually Use Them
Source: Dev.to
JavaScript Design Patterns — What Really Matters for Front‑End Development
JavaScript applications grow fast — more features, more modules, more complexity. Without structure, even senior developers end up drowning in technical debt.
Design patterns help you
- ✅ Organize code
- ✅ Avoid duplication
- ✅ Improve maintainability
- ✅ Scale features safely
- ✅ Build predictable architecture
Not every classical design pattern makes sense in JavaScript. Below are the patterns that are truly relevant to JS and are used in real‑world front‑end projects.
1. Singleton Pattern
What it is
A pattern that ensures only one instance of an object exists.
Example
class AuthService {
constructor() {
if (AuthService.instance) return AuthService.instance;
this.user = null;
AuthService.instance = this;
}
}
When to use
- Auth manager
- Theme manager
- App configuration
- Global caching layer
Real‑world use
Your React/Angular app shouldn’t create multiple authentication handlers. A singleton guarantees a single source of truth for the user session.
2. Module Pattern
What it is
Encapsulates private logic and exposes only what’s needed.
Example
const Cart = (() => {
let items = [];
function add(item) { items.push(item); }
function get() { return items; }
return { add, get };
})();
When to use
- Utility helpers
- Data services
- Local‑storage wrappers
- Analytics modules
Real‑world use
A storageService shouldn’t expose internal keys or prefixes. The module pattern keeps those details private while providing a clean public API.
3. Observer Pattern
What it is
Allows one object to notify multiple listeners when something changes.
Example
class Observable {
constructor() { this.subscribers = []; }
subscribe(fn) { this.subscribers.push(fn); }
notify(value) { this.subscribers.forEach(fn => fn(value)); }
}
When to use
- Event systems
- UI updates
- Real‑time data streams
- Pub‑sub based apps
Real‑world use
- UI state updates in vanilla JS
- Notification centers
- WebSocket live updates
- Custom event emitters in React, Vue, or Node.js
4. Factory Pattern
What it is
Creates objects without exposing the creation logic.
Example
function createUser(type) {
if (type === "admin") return { role: "admin", canDelete: true };
return { role: "user", canDelete: false };
}
When to use
- Conditional object creation
- Multiple API versions
- Data adapters
Real‑world use
Payment integrations (Stripe, PayPal, Razorpay) return different response shapes. A factory normalises them into a common interface for the rest of the app.
5. Strategy Pattern
What it is
Defines interchangeable algorithms and selects one dynamically.
Example
const feeStrategies = {
credit: amt => amt * 0.02,
upi: amt => amt * 0,
debit: amt => amt * 0.01,
};
function calculateFee(method, amount) {
return feeStrategies[method](amount);
}
When to use
- Dynamic business logic
- Replacing long
if‑elsechains - Pricing or discount calculations
Real‑world use
- Payment fee calculation
- Sorting functions for lists
- Feature‑toggle based behaviour
6. Decorator Pattern
What it is
Wraps a function or object to extend behaviour without modifying the original code.
Example
function withLog(fn) {
return (...args) => {
console.log("Running", fn.name);
return fn(...args);
};
}
When to use
- Logging
- Caching
- Throttling / Debouncing
- Authorization wrappers
Real‑world use
- React higher‑order components (
withAuth) - Axios interceptors
- Performance‑measurement wrappers
7. Proxy Pattern
What it is
Intercepts interactions with an object.
Example
const user = { name: "Ani" };
const proxy = new Proxy(user, {
get(target, prop) {
console.log("Access:", prop);
return target[prop];
}
});
When to use
- Validation
- Access control
- Reactivity
- Memoization
Real‑world use
Vue 3’s entire reactivity system is built on Proxy.
8. Command Pattern
What it is
Encapsulates actions as objects (execute + undo).
Example
class Command {
constructor(execute, undo) {
this.execute = execute;
this.undo = undo;
}
}
When to use
- Undo/redo functionality
- History tracking
- Macro actions
Real‑world use
- Figma undo stack
- VS Code command palette
- Browser‑like navigation history
9. Adapter Pattern
What it is
Converts an incompatible interface to a usable one.
Example
function axiosToFetch(response) {
return {
status: response.status,
data: response.data,
};
}
When to use
- Migrating codebases
- Integrating third‑party APIs
- Normalising inconsistent data
Real‑world use
Wrap axios responses into a unified format so your API