JavaScript is a flexible and expressive language. But with flexibility comes responsibility—especially as applications grow larger and more complex. One of the most essential patterns to structure and manage code better is the Module Pattern.
The Module Pattern helps you organize your code into reusable, self-contained units, enabling better encapsulation, separation of concerns, and namespace management.
The Module Pattern is a design pattern used to:
It relies on closures and immediately invoked function expressions (IIFE) to create private scope.
Here’s a simple example of the pattern:
const CounterModule = (function () { let count = 0; // private variable function changeBy(val) { count += val; } return { increment() { changeBy(1); }, decrement() { changeBy(-1); }, value() { return count; } }; })();
count and changeBy are private to the module.increment, decrement, and value are exposed publicly.const MathUtils = (function () { return { square(n) { return n * n; }, cube(n) { return n * n * n; } }; })(); console.log(MathUtils.square(3)); // 9
const Modal = (function () { let isVisible = false; function show() { isVisible = true; console.log("Modal is now visible"); } function hide() { isVisible = false; console.log("Modal hidden"); } return { open: show, close: hide }; })(); Modal.open(); Modal.close();
const Auth = (function () { let user = null; return { login(name) { user = name; }, logout() { user = null; }, getUser() { return user; } }; })();
| Feature | Description |
|---|---|
| Private Members | Maintained via closures |
| Public API | Defined in the returned object |
| Singleton | Only one instance exists |
| Encapsulation | Promotes separation of concerns |
Not reusable as multiple instances Since it’s an IIFE, the module is a singleton.
Testing private members Private state cannot be directly tested unless exposed.
Not dynamic You can't parameterize or reset private state easily without modifying the core structure.
With ES6, we now have native modules using import/export syntax:
mathUtils.jslet count = 0; export function increment() { count++; } export function getCount() { return count; }
main.jsimport { increment, getCount } from './mathUtils.js'; increment(); console.log(getCount()); // 1
✅ These modules are:
While ES6 modules are preferred for modern applications, the classic Module Pattern is still useful:
| Module Pattern Benefits | Notes |
|---|---|
| ✔ Encapsulates private data | Using closures and IIFE |
| ✔ Exposes clean public API | via return object |
| ✔ Avoids global clutter | Contains code within function scope |
| ❌ Singleton only | No support for multiple instances |
| ❌ Limited flexibility | No parameterization or dynamic setup |
The Module Pattern is a cornerstone of JavaScript architecture that laid the groundwork for modern module systems. It helps write clean, modular, and maintainable code by enforcing encapsulation and separation of concerns. Understanding this pattern also provides deep insight into how closures, scoping, and privacy work in JavaScript.
Test your understanding with 3 quick questions