Umar Farooque Khan
3 min readNov 14, 2023

Unraveling JavaScript Design Patterns with Practical Examples

Introduction:

JavaScript design patterns offer solutions to recurring problems in software design. In this article, we’ll explore essential design patterns with real-world examples to deepen our understanding of their application and benefits.

1. Singleton Pattern: Ensuring Single Instances

Example:

var Singleton = (function () {
var instance;
function createInstance() {
// Private constructor logic here
return { /* Singleton instance properties/methods */ };
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Usage
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

Explanation:

The Singleton pattern restricts instantiation to a single instance and provides a global access point. In this example, createInstance is a private function that constructs the Singleton object. The getInstance method ensures only one instance is created and returned.

2. Module Pattern: Encapsulation for Better Structure

Example:

var Module = (function () {
var privateVariable = 'I am private';
function privateMethod() {
console.log('This is private');
}
return {
publicVariable: 'I am public',
publicMethod: function () {
console.log('This is public');
}
};
})();
// Usage
console.log(Module.publicVariable); // 'I am public'
Module.publicMethod(); // 'This is public'
console.log(Module.privateVariable); // undefined (cannot access private variable)
Module.privateMethod(); // Error (cannot call private method)

Explanation:

The Module pattern encapsulates private variables and functions, exposing only the necessary parts publicly. This enhances code organization and prevents pollution of the global namespace.

3. Observer Pattern: Responding to Changes Efficiently

Example:

function Subject() {
this.observers = [];
this.addObserver = function (observer) {
this.observers.push(observer);
};
this.notifyObservers = function () {
this.observers.forEach(function (observer) {
observer.update();
});
};
}
function Observer() {
this.update = function () {
console.log('State updated');
};
}
// Usage
var subject = new Subject();
var observer1 = new Observer();
var observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers(); // 'State updated' (logged for bothobservers)

Explanation:

The Observer pattern establishes a dependency between objects. In this example, the Subject maintains a list of observers and notifies them when its state changes. Observers, like observer1 and observer2, respond to the update.

4. Factory Pattern: Creating Objects Seamlessly

Example:

function Car(model, year) {
this.model = model;
this.year = year;
}
function CarFactory() {
this.createCar = function (model, year) {
return new Car(model, year);
};
}

// Usage
var factory = new CarFactory();
var car1 = factory.createCar('Toyota', 2022);
var car2 = factory.createCar('Honda', 2021);

Explanation:

The Factory pattern provides an interface for creating objects. Here, CarFactory encapsulates the logic for creating Car instances, promoting loose coupling and code flexibility.

5. Decorator Pattern: Adding Functionality Dynamically

Example:

function Coffee() {
this.cost = function () {
return 5;
};
}
function MilkDecorator(coffee) {
this.cost = function () {
return coffee.cost() + 2;
};
}

function SugarDecorator(coffee) {
this.cost = function () {
return coffee.cost() + 1;
};
}

// Usage
var myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);

console.log(myCoffee.cost()); // 8

Explanation:

The Decorator pattern dynamically adds functionality to objects. In this example, MilkDecorator and SugarDecorator enhance the cost calculation of a Coffee object.

6. MVC Pattern: Separation of Concerns

Example:

// Model
function UserModel() {
this.data = [];
this.addData = function (item) {
this.data.push(item);
};
}
// View
function UserView(model) {
this.render = function () {
console.log('Rendered user data:', model.data);
};
}
// Controller
function UserController(model, view) {
this.addUser = function (user) {
model.addData(user);
view.render();
};
}
// Usage
var userModel = new UserModel();
var userView = new UserView(userModel);
var userController = new UserController(userModel, userView);
userController.addUser('John Doe');

Explanation:

The MVC pattern separates concerns in an application. UserModel handles data, UserView renders the data, and UserController manages user interactions, ensuring a clean and organized structure.

Conclusion:

JavaScript design patterns are powerful tools for crafting maintainable and efficient code. By understanding and applying patterns like Singleton, Module, Observer, Factory, Decorator, and MVC, developers can elevate their coding practices, resulting in more scalable and readable applications. These examples provide a practical foundation for incorporating these patterns into your own JavaScript projects.

Here are some additional high-quality tutorials for you to explore:

  1. JavaScript interview Question and Answer
  2. Node Js Interview Question and Answer
  3. JavaScript Tricky Question
Umar Farooque Khan

Experienced software developer with a passion for clean code and problem-solving. Full-stack expertise in web development. Lifelong learner and team player.