Module Pattern in JavaScript
January 4, 2026
Module Pattern in JavaScript
The Module Pattern is a design pattern in JavaScript that allows you to group related variables and functions together into a single object, providing encapsulation and a way to create private and public members. This pattern helps organize code, prevent global namespace pollution, and create more maintainable applications.
What is the Module Pattern?
The Module Pattern uses closures and immediately invoked function expressions (IIFE) to create private scope and expose only the public API. It's one of the most fundamental patterns in JavaScript for organizing code.
Basic Implementation
Simple Module Pattern
const module = (function () {
// Private variable
let privateVariable = "I'm private";
// Private function
function privateMethod() {
return "private method";
}
// Public API
return {
publicMethod: function () {
console.log(privateMethod()); // Can access private method
console.log("public");
},
};
})();
// Usage
module.publicMethod(); // Prints: "private method" and "public"
// module.privateMethod(); // Error: module.privateMethod is not a function
Enhanced Module Pattern
function ModulePattern() {
const name = "Ashish";
const age = 24;
// Private function
function addSomeAgeValue() {
return 2;
}
// Private function that uses other private members
function calculateAge() {
return age + addSomeAgeValue();
}
// Public API
return {
returnName: function () {
return name;
},
calculateAge, // Expose calculateAge
};
}
const moduleMethod = new ModulePattern();
console.log(moduleMethod.returnName()); // "Ashish"
console.log(moduleMethod.calculateAge()); // 26
// console.log(moduleMethod.addSomeAgeValue()); // Error: not a function
Key Concepts
1. Private Members
Variables and functions declared inside the module are private and cannot be accessed from outside:
const counter = (function () {
let count = 0; // Private variable
return {
increment: function () {
count++;
},
getCount: function () {
return count;
},
};
})();
counter.increment();
console.log(counter.getCount()); // 1
// console.log(counter.count); // undefined - private!
2. Public API
Only the returned object is accessible from outside the module:
const calculator = (function () {
// Private
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// Public
return {
add,
subtract,
// Private functions remain hidden
};
})();
calculator.add(5, 3); // 8
// calculator.multiply is not accessible
3. Namespace Protection
Modules prevent global namespace pollution:
// Without module pattern - pollutes global scope
let count = 0;
function increment() {
count++;
}
// With module pattern - encapsulated
const counterModule = (function () {
let count = 0;
return {
increment: () => count++,
getCount: () => count,
};
})();
Advanced Patterns
1. Revealing Module Pattern
Expose private functions by reference rather than defining them in the return object:
const userModule = (function () {
let users = [];
function addUser(user) {
users.push(user);
}
function removeUser(id) {
users = users.filter((user) => user.id !== id);
}
function getUser(id) {
return users.find((user) => user.id === id);
}
function getAllUsers() {
return users.slice(); // Return copy to prevent external modification
}
// Reveal only what's needed
return {
add: addUser,
remove: removeUser,
get: getUser,
getAll: getAllUsers,
};
})();
userModule.add({ id: 1, name: "John" });
console.log(userModule.getAll()); // [{ id: 1, name: "John" }]
2. Module with Dependencies
Pass dependencies as parameters:
const dataModule = (function (jQuery, lodash) {
// Use jQuery and lodash here
function processData(data) {
const processed = lodash.map(data, (item) => item * 2);
jQuery("#result").text(processed.join(", "));
}
return {
process: processData,
};
})(jQuery, _); // Dependencies injected
3. Singleton Module
Ensure only one instance exists:
const singletonModule = (function () {
let instance;
function createInstance() {
return {
data: [],
add: function (item) {
this.data.push(item);
},
};
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const instance1 = singletonModule.getInstance();
const instance2 = singletonModule.getInstance();
console.log(instance1 === instance2); // true - same instance
4. Module with State
Maintain state across method calls:
const stateModule = (function () {
let state = {
count: 0,
name: "Default",
};
function setState(newState) {
state = { ...state, ...newState };
}
function getState() {
return { ...state }; // Return copy
}
return {
setState,
getState,
};
})();
stateModule.setState({ count: 5, name: "Updated" });
console.log(stateModule.getState()); // { count: 5, name: "Updated" }
Real-World Examples
Example 1: API Client Module
const apiClient = (function () {
const baseURL = "https://api.example.com";
let token = null;
// Private function
function makeRequest(endpoint, options = {}) {
const url = `${baseURL}${endpoint}`;
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: token ? `Bearer ${token}` : "",
},
});
}
// Public API
return {
setToken: function (newToken) {
token = newToken;
},
get: function (endpoint) {
return makeRequest(endpoint, { method: "GET" });
},
post: function (endpoint, data) {
return makeRequest(endpoint, {
method: "POST",
body: JSON.stringify(data),
});
},
};
})();
apiClient.setToken("abc123");
apiClient.get("/users").then(/* ... */);
Example 2: DOM Manipulation Module
const domUtils = (function () {
// Private cache
const cache = new Map();
// Private helper
function getCachedElement(selector) {
if (!cache.has(selector)) {
cache.set(selector, document.querySelector(selector));
}
return cache.get(selector);
}
// Public API
return {
show: function (selector) {
const element = getCachedElement(selector);
if (element) element.style.display = "block";
},
hide: function (selector) {
const element = getCachedElement(selector);
if (element) element.style.display = "none";
},
toggle: function (selector) {
const element = getCachedElement(selector);
if (element) {
element.style.display =
element.style.display === "none" ? "block" : "none";
}
},
};
})();
domUtils.show("#myElement");
Example 3: Configuration Module
const config = (function () {
const settings = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
return {
get: function (key) {
return settings[key];
},
set: function (key, value) {
if (key in settings) {
settings[key] = value;
}
},
getAll: function () {
return { ...settings }; // Return copy
},
};
})();
console.log(config.get("apiUrl")); // "https://api.example.com"
config.set("timeout", 10000);
Benefits
- Encapsulation: Private members are protected from external access
- Namespace Management: Avoids global variable pollution
- Code Organization: Groups related functionality together
- Maintainability: Easier to maintain and update
- Reusability: Modules can be easily reused across projects
- Testing: Easier to test isolated modules
Limitations
- No True Privacy: Private members can still be accessed through debugging tools
- Memory: Each module instance creates its own closure
- No Inheritance: Traditional module pattern doesn't support inheritance well
- Testing Private Methods: Hard to test private functions directly
Modern Alternatives
ES6 Modules
// math.js
let privateVar = 0;
export function add(a, b) {
return a + b;
}
export function getPrivateVar() {
return privateVar;
}
// main.js
import { add, getPrivateVar } from "./math.js";
Class-based Modules
class Calculator {
#privateField = 0; // Private field (ES2022)
#privateMethod() {
return "private";
}
publicMethod() {
return this.#privateMethod();
}
}
Best Practices
- Single Responsibility: Each module should have one clear purpose
- Minimal Public API: Expose only what's necessary
- Documentation: Document the public API
- Consistent Naming: Use consistent naming conventions
- Dependency Injection: Pass dependencies as parameters
- Avoid Global State: Keep state within the module
Comparison with Other Patterns
| Pattern | Use Case | Inheritance | Privacy | | -------------- | -------------------- | ----------- | -------------- | | Module Pattern | Simple encapsulation | No | Closure-based | | Class Pattern | OOP-style | Yes | Private fields | | ES6 Modules | File-based modules | No | File scope |
Summary
The Module Pattern is a fundamental JavaScript pattern that provides:
- Encapsulation through closures
- Private and public members
- Namespace protection
- Code organization
While modern JavaScript offers ES6 modules and classes, the Module Pattern remains valuable for:
- Legacy codebases
- Browser compatibility
- Understanding JavaScript fundamentals
- Creating lightweight, self-contained modules
Understanding the Module Pattern is essential for writing well-organized, maintainable JavaScript code.