JavaScript gives us a lot of freedom—but with great flexibility comes the risk of messy code. That's where design patterns come in.
Design patterns are reusable solutions to common software design problems. They aren't libraries or frameworks; they're proven approaches that help you write code that's easier to maintain, extend, and understand.
Let's explore some of the most useful JavaScript design patterns with examples.
1. Module Pattern
The Module Pattern helps encapsulate data and expose only what's necessary.
const Counter = (() => {
let count = 0;
return {
increment() {
count++;
},
decrement() {
count--;
},
getCount() {
return count;
}
};
})();
Counter.increment();
console.log(Counter.getCount()); // 1
Why use it?
- Data privacy
- Avoid global variables
- Better organization
2. Singleton Pattern
A Singleton ensures only one instance of an object exists.
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
this.connection = "Connected";
Database.instance = this;
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
Common Uses
- Database connections
- Configuration objects
- Logging services
3. Factory Pattern
Instead of using new everywhere, a Factory creates objects based on input.
class Car {
drive() {
console.log("Driving a car");
}
}
class Bike {
ride() {
console.log("Riding a bike");
}
}
function vehicleFactory(type) {
if (type === "car") return new Car();
if (type === "bike") return new Bike();
}
const vehicle = vehicleFactory("car");
vehicle.drive();
Benefits
- Centralized object creation
- Easy to add new object types
- Reduces duplicated code
4. Observer Pattern
Used when multiple objects need updates after one object's state changes.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach(observer => observer(data));
}
}
const news = new Subject();
news.subscribe(message => {
console.log("Subscriber 1:", message);
});
news.subscribe(message => {
console.log("Subscriber 2:", message);
});
news.notify("New JavaScript update!");
Real-world Examples
- Event listeners
- Redux
- React state updates
- Notifications
5. Prototype Pattern
Objects inherit behavior from other objects.
const person = {
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const john = Object.create(person);
john.name = "John";
john.greet();
Benefits
- Memory efficient
- Shared methods
- JavaScript's native inheritance model
6. Strategy Pattern
Choose an algorithm at runtime.
const paymentStrategies = {
creditCard(amount) {
console.log(`Paid $${amount} with Credit Card`);
},
paypal(amount) {
console.log(`Paid $${amount} with PayPal`);
}
};
function checkout(amount, strategy) {
strategy(amount);
}
checkout(100, paymentStrategies.creditCard);
checkout(50, paymentStrategies.paypal);
Use Cases
- Payment gateways
- Sorting algorithms
- Authentication methods
7. Decorator Pattern
Adds new behavior without modifying existing code.
function coffee() {
return "Coffee";
}
function withMilk(fn) {
return () => fn() + " + Milk";
}
function withSugar(fn) {
return () => fn() + " + Sugar";
}
const order = withSugar(withMilk(coffee));
console.log(order());
Output:
Coffee + Milk + Sugar
8. Adapter Pattern
Allows incompatible interfaces to work together.
class OldAPI {
oldMethod() {
return "Old API";
}
}
class Adapter {
constructor(api) {
this.api = api;
}
newMethod() {
return this.api.oldMethod();
}
}
const adapter = new Adapter(new OldAPI());
console.log(adapter.newMethod());
9. Command Pattern
Encapsulates requests as objects.
class Light {
on() {
console.log("Light ON");
}
}
class LightCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.on();
}
}
const light = new Light();
const command = new LightCommand(light);
command.execute();
10. MVC Pattern
Separates application logic into:
- Model → Data
- View → UI
- Controller → Business logic
Many JavaScript frameworks (or their predecessors) have adopted versions of this architecture.
Which Pattern Should You Use?
| Pattern | Best For |
|---|---|
| Module | Encapsulation |
| Singleton | Single shared instance |
| Factory | Object creation |
| Observer | Event systems |
| Prototype | Inheritance |
| Strategy | Runtime algorithm selection |
| Decorator | Extend functionality |
| Adapter | Compatibility |
| Command | Undo/redo, task queues |
| MVC | Large applications |
Final Thoughts
Design patterns are tools—not rules. Overusing them can make code more complex than necessary, but applying the right pattern to the right problem can improve readability, maintainability, and scalability.
As you build larger JavaScript applications, you'll likely encounter many of these patterns naturally in frameworks and libraries. Understanding the underlying ideas will help you recognize when a pattern fits your own codebase and when a simpler solution is enough.
Happy coding!
Top comments (0)