π Design Patterns in JavaScript β Explained with Practical Examples
Design patterns are reusable solutions to common software design problems.
Instead of memorizing definitions, understand this:
A design pattern solves a recurring engineering problem.
In this article, weβll walk through Creational, Structural, and Behavioral patterns with simple JavaScript examples.
π CREATIONAL PATTERNS
(How objects are created)
1οΈβ£ Factory Pattern
π§ Core Idea
Hide object creation logic and delegate it to subclasses.
β Problem
You donβt want object creation logic scattered everywhere.
β Example
class UPI {
constructor() {
console.log('upi class');
}
}
class Card {
constructor() {
console.log('card class');
}
}
class PaymentFactory {
createPayment(type) {
if (type === 'UPI') {
return new UPI();
} else if (type === 'Card') {
return new Card();
}
}
}
const paymentFactory = new PaymentFactory();
paymentFactory.createPayment('UPI');
π― When to Use
- When object creation is complex
- When multiple subclasses exist
- When you want to hide instantiation logic
2οΈβ£ Builder Pattern
π§ Core Idea
Construct complex objects step by step.
β Problem
Too many constructor parameters make code unreadable.
β Example
class Car {
constructor() {
this.car = {};
}
setCar(car) {
this.car = car;
return this;
}
getCar() {
return this.car;
}
}
class CarBuilder {
constructor() {
this.car = new Car();
}
build() {
return this.car;
}
}
const carBuilder = new CarBuilder();
const car = carBuilder
.build()
.setCar({
name: 'Car',
color: 'Red',
price: 1000000
})
.getCar();
π― When to Use
- When object creation involves many optional fields
- When readability matters
3οΈβ£ Singleton Pattern
π§ Core Idea
Only one instance of a class should exist.
β Problem
You donβt want multiple DB connections or config objects.
β Example
class DBConnection {
static instance;
constructor() {
if (DBConnection.instance) return DBConnection.instance;
DBConnection.instance = this;
}
}
π― When to Use
- Logger
- Database connection
- Configuration manager
4οΈβ£ Prototype Pattern
π§ Core Idea
Clone an existing object instead of creating a new one.
β Problem
Object creation is expensive.
β Example
const existingObject = {
name: 'Car',
color: 'Red',
price: 1000000
};
const newObject = Object.create(existingObject);
console.log(newObject.color);
π― When to Use
- When object creation is costly
- When cloning is cheaper than instantiation
π§± STRUCTURAL PATTERNS
(How objects are composed)
5οΈβ£ Facade Pattern
π§ Core Idea
Hide complex logic behind a simple interface.
β Problem
Clients shouldnβt know internal system complexity.
β Example
class AuthService {
check() {}
}
class OrderService {
process() {}
}
class PaymentProcessor {
constructor() {
this.payment = new PaymentFactory();
this.order = new OrderService();
this.auth = new AuthService();
}
placeOrder() {
this.auth.check();
this.order.process();
this.payment.createPayment('UPI');
}
}
const paymentProcessor = new PaymentProcessor();
paymentProcessor.placeOrder();
π― When to Use
- To simplify large subsystems
- To reduce coupling
6οΈβ£ Adapter Pattern
π§ Core Idea
Convert one interface into another expected by the client.
β Problem
Legacy code doesnβt match your new interface.
β Example
class OldApi {
getUser() {
console.log('adaptor pattern');
}
}
class NewApi {
constructor() {
this.oldApi = new OldApi();
}
getUser() {
return this.oldApi.getUser();
}
}
const newApi = new NewApi();
newApi.getUser();
π― When to Use
- Integrating third-party libraries
- Migrating legacy systems
7οΈβ£ Decorator Pattern
π§ Core Idea
Add functionality without modifying original code.
β Problem
You want to extend behavior dynamically.
β Example
function logger(fn) {
return (...args) => {
console.log('Running...');
return fn.apply(this, args);
};
}
function sum(a, b) {
console.log(a + b);
}
const loggerSum = logger(sum);
loggerSum(1, 2);
π― When to Use
- Logging
- Caching
- Authentication wrappers
π§ BEHAVIORAL PATTERNS
(How objects communicate and behave)
8οΈβ£ Strategy Pattern
π§ Core Idea
Encapsulate algorithms and make them interchangeable.
β Problem
Too many if-else conditions for similar behaviors.
β Example
class Strategy {
execute() {
throw new Error('Must implement execute');
}
}
class ConcreteStrategyA extends Strategy {
execute() {
console.log('Executing strategy A');
}
}
class ConcreteStrategyB extends Strategy {
execute() {
console.log('Executing strategy B');
}
}
class Context {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executeStrategy() {
this.strategy.execute();
}
}
const context = new Context(new ConcreteStrategyA());
context.executeStrategy();
context.setStrategy(new ConcreteStrategyB());
context.executeStrategy();
9οΈβ£ Observer Pattern
π§ Core Idea
Notify multiple objects when state changes.
β Problem
Many components depend on one objectβs state.
β Example
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
notifyObservers() {
this.observers.forEach(observer => observer.update());
}
}
class ConcreteObserver {
update() {
console.log('Observer updated');
}
}
const subject = new Subject();
const observer = new ConcreteObserver();
subject.addObserver(observer);
subject.notifyObservers();
π State Pattern
π§ Core Idea
Behavior changes when internal state changes.
β Problem
Too many conditional state checks.
β Example
class State {
execute() {
throw new Error('Must implement execute');
}
}
class ConcreteStateA extends State {
execute() {
console.log('Executing state A');
}
}
class ConcreteStateB extends State {
execute() {
console.log('Executing state B');
}
}
class Context {
constructor(state) {
this.state = state;
}
setState(state) {
this.state = state;
}
executeState() {
this.state.execute();
}
}
const context = new Context(new ConcreteStateA());
context.executeState();
context.setState(new ConcreteStateB());
context.executeState();
1οΈβ£1οΈβ£ Command Pattern
π§ Core Idea
Encapsulate a request as an object.
β Problem
You want to queue, log, or undo actions.
β Example
class Command {
execute() {
throw new Error('Must implement execute');
}
}
class ConcreteCommandA extends Command {
execute() {
console.log('Executing command A');
}
}
class ConcreteCommandB extends Command {
execute() {
console.log('Executing command B');
}
}
class Context {
constructor(command) {
this.command = command;
}
setCommand(command) {
this.command = command;
}
executeCommand() {
this.command.execute();
}
}
const context = new Context(new ConcreteCommandA());
context.executeCommand();
context.setCommand(new ConcreteCommandB());
context.executeCommand();
π― Final Takeaway
Instead of memorizing names, remember problems:
| Problem | Pattern |
|---|---|
| Hide object creation | Factory |
| Build complex object | Builder |
| Only one instance | Singleton |
| Clone object | Prototype |
| Simplify complex system | Facade |
| Convert interface | Adapter |
| Extend functionality | Decorator |
| Replace if-else logic | Strategy |
| Notify subscribers | Observer |
| Behavior changes by state | State |
| Encapsulate request | Command |
Top comments (0)