JavaScript applications grow fast — more features, more modules, more complexity. Without structure, even senior developers end up drowning in tech debt.
Design patterns help you:
✔ Organize code
✔ Avoid duplication
✔ Improve maintainability
✔ Scale features safely
✔ Build predictable architecture
But not all classical design patterns make sense in JavaScript.
This article focuses only on patterns that are truly relevant to JS and used in real-world frontend development.
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 ensures global consistency.
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
Your storageService shouldn't expose internals like keys or prefixes — module pattern keeps everything safe and clean.
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
- Pub-sub based apps
Real-World Use
- UI state updates in vanilla JS
- Notifications
- WebSocket live updates
- Custom event emitters in React, Vue, or Node.js
4. Factory Pattern
What It Is
Creates objects without exposing 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 may return different shapes — a factory normalizes them.
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
- Replace long
if-elsechains - Pricing or discount calculations
Real-World Use
- Payment fee calculation
- Sorting functions for lists
- Feature toggles
6. Decorator Pattern
What It Is
Wraps a function or object to extend behavior without modifying input 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 with 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
- Normalizing inconsistent data
Real-World Use
Converting axios responses into a unified format so your API layer works with both Fetch and Axios.
10. Mediator Pattern
What It Is
The Mediator pattern introduces a central controller that manages communication between multiple objects or components, preventing them from directly referencing each other.
Instead of components talking to each other directly (tight coupling), they communicate through the mediator.
Example
class UIEventMediator {
constructor() {
this.components = {};
}
register(name, component) {
this.components[name] = component;
component.setMediator(this);
}
notify(event, data) {
switch (event) {
case "USER_LOGGED_IN":
this.components.navbar.updateUser(data);
this.components.sidebar.showMenu();
break;
case "USER_LOGGED_OUT":
this.components.navbar.clearUser();
this.components.sidebar.hideMenu();
break;
}
}
}
// Components
class Navbar {
setMediator(mediator) {
this.mediator = mediator;
}
updateUser(user) {
console.log("Navbar updated with user:", user.name);
}
clearUser() {
console.log("Navbar cleared");
}
}
class Sidebar {
setMediator(mediator) {
this.mediator = mediator;
}
showMenu() {
console.log("Sidebar menu shown");
}
hideMenu() {
console.log("Sidebar menu hidden");
}
}
// Usage
const mediator = new UIEventMediator();
const navbar = new Navbar();
const sidebar = new Sidebar();
mediator.register("navbar", navbar);
mediator.register("sidebar", sidebar);
// Trigger event
mediator.notify("USER_LOGGED_IN", { name: "Ani" });
When to Use
- Multiple UI components depend on the same events
- Components should not directly reference each other
- Communication logic is getting scattered
- You want a single source of truth for interactions
Real-World Use Cases
- Modal systems (open/close affecting multiple components)
- Authentication flows (login affects navbar, sidebar, permissions)
- Dashboards where widgets react to global events
- Complex forms with dependent fields
- Micro-frontend communication layer
Real-Life Use Cases for These Patterns
Here’s how these patterns map to daily frontend work:
| Scenario | Pattern | Why |
|---|---|---|
| Global auth / theme / config | Singleton | Shared state consistency |
| API services, storage utils | Module | Encapsulation + maintainability |
| UI reacting to data changes | Observer | One-to-many updates |
| Dynamic object creation | Factory | Clean, scalable creation logic |
| Switchable business rules | Strategy | Removes if-else chains |
| Wrapping logic with caching/logging | Decorator | Non-intrusive behavior |
| Input validation or state tracking | Proxy | Fine-grained control |
| Undo/Redo UI interactions | Command | Action history |
| Integrating external tools | Adapter | Makes different APIs work |
| Coordinating multiple UI components | Mediator | Centralized, decoupled communication |
Final Thoughts
Design patterns aren’t magic. They’re tools to make your JavaScript cleaner, scalable, testable, and more maintainable.
Use them when they solve a real problem — not because they're “best practice.”
Top comments (0)