Understanding the Module Pattern in JavaScript


JavaScript is a language that encourages flexibility—but sometimes, that flexibility can create messy code. That’s where the Module Pattern comes in.
In this article, we’ll explore what the Module Pattern is, when and why to use it, and how to implement it with clear, real-world examples.
🧠 What Is the Module Pattern?
The Module Pattern is a design pattern that helps you encapsulate code into small, reusable, and private units. It allows you to expose only what’s necessary, keeping the rest of the data/functions hidden from the outside world.
Think of it like a lunchbox. You open the box and only see what's meant to be eaten. The recipe and preparation stay in the kitchen (hidden from you).
📚 Topics We’ll Cover
Why use the Module Pattern?
Syntax of the Module Pattern
Public vs Private variables/functions
Practical example: Cart module in an e-commerce app
When not to use it
❓ Why Use the Module Pattern?
Avoid polluting the global scope: Helps keep variables/functions from clashing
Encapsulation: Keeps internal logic hidden
Better organization: Clean structure, especially in large codebases
Improved maintainability
🧪 Basic Syntax
const myModule = (function () {
// private variable
let counter = 0;
// private function
function log(message) {
console.log("Log:", message);
}
// public API
return {
increment: function () {
counter++;
log(`Counter: ${counter}`);
},
getCount: function () {
return counter;
},
};
})();
Usage
myModule.increment(); // Log: Counter: 1
console.log(myModule.getCount()); // 1
You cannot access counter
or log()
directly from outside. That’s the magic of this pattern.
🛒 Real Example: E-commerce Cart Module
Let’s say you're building an e-commerce site. You need a cart
module that:
Adds items to cart
Removes items
Shows all items
Doesn’t expose the cart array directly
const cartModule = (function () {
let cart = [];
function findItemIndex(id) {
return cart.findIndex(item => item.id === id);
}
return {
addItem: function (item) {
cart.push(item);
console.log(`${item.name} added to cart`);
},
removeItem: function (id) {
const index = findItemIndex(id);
if (index > -1) {
const removed = cart.splice(index, 1);
console.log(`${removed[0].name} removed from cart`);
}
},
showItems: function () {
console.table(cart);
},
};
})();
Usage:
cartModule.addItem({ id: 1, name: "MacBook" });
cartModule.addItem({ id: 2, name: "iPhone" });
cartModule.showItems();
cartModule.removeItem(1);
cartModule.showItems();
❌ Try this:
console.log(cartModule.cart); // undefined 🙅♂️
The internal cart
array is completely private!
👎 When Not to Use Module Pattern
In large-scale applications using ES Modules or frameworks like React, Vue, etc.
When dynamic dependency management is needed
When bundlers like Webpack or Vite are used, ES6 modules are preferred
✅ When to Use
Small/medium apps without build tools
Vanilla JS projects needing structure
Encapsulating logic without third-party libraries
💡 Pro Tips
You can use IIFE (Immediately Invoked Function Expression) to create the module
Don’t overuse it—modularization should serve clarity, not complexity
Combine with factory functions for more flexibility
🧾 Summary
The Module Pattern is an old but gold way to write modular and clean JavaScript. It's still useful in many scenarios and helps keep your code private, secure, and tidy.
Think of it as a way to pack your code with labels: "For internal use only" vs "Safe to access."
🔚 Final Thoughts
Even though ES6 Modules are the standard now, the Module Pattern is still relevant in many situations, especially when working without tooling or writing libraries that run in the browser directly.
Use it wisely, and your code will thank you!
Subscribe to my newsletter
Read articles from Yasir M directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
