Online Compiler logoOnline Compiler
Advanced Patterns

Module Pattern: Creating Encapsulation with Closures

The Module Pattern leverages closures to create private and public interfaces, enabling data encapsulation and namespace management. It's a powerful design pattern that forms the foundation of modern modular JavaScript development.

What is the Module Pattern?

The Module Pattern uses closures to create private scope and expose a selective public interface. It combines Immediately Invoked Function Expressions (IIFE) with closures to create self-contained modules with controlled access to internal state and functionality.

Basic Module Pattern

const Counter = (function() {
  // Private variables
  let count = 0;
  
  // Private functions
  function log(msg) {
    console.log('[Counter] ' + msg);
  }
  
  // Public API (returned object)
...

The module creates a private scope with hidden count variable. Only methods in the returned object can access it.

Core Concepts

IIFE (Immediately Invoked Function Expression)

IIFE Creates Module Scope

// IIFE: Function defined and called immediately
(function() {
  let privateVar = 'private';
  console.log(privateVar); // 'private' - only accessible here
})();

// console.log(privateVar); // ReferenceError

// Module returns public interface
const Module = (function() {
...

IIFE creates a temporary scope. Returning an object creates closures that remember that scope.

Data Privacy and Encapsulation

Private Variables Cannot Be Modified Externally

const BankAccount = (function() {
  let balance = 1000; // Truly private, cannot be accessed directly
  
  return {
    deposit: function(amount) {
      if (amount > 0) {
        balance += amount;
        console.log('Deposited: ' + amount);
      }
    },
...

Private variables like balance cannot be accessed or modified from outside. Only the public methods can modify them.

Module Pattern Variations

Revealing Module Pattern

Define Functions, Reveal Only Public

const Calculator = (function() {
  // Private: Define all functions first
  function add(x, y) {
    return x + y;
  }
  
  function subtract(x, y) {
    return x - y;
  }
  
...

All functions are defined privately. The return statement selectively reveals only those needed in the public API.

Advanced Module Patterns

Singleton Pattern

Module Ensures Single Instance

const DatabaseConnection = (function() {
  let instance = null;
  
  function createConnection() {
    return {
      id: Math.random().toString(36).slice(2, 9),
      connected: true,
      query: function(sql) {
        console.log('Executing: ' + sql);
        return 'results';
...

The module pattern naturally creates singletons. Each module is instantiated once and reused.

Module with Dependencies

Passing Dependencies as Parameters

// User module
const UserModule = (function() {
  let users = [];
  return {
    add: (name) => users.push(name),
    getAll: () => users.slice(),
    count: () => users.length
  };
})();

...

Modules can accept dependencies as parameters. This enables better testing and loose coupling.

Augmentation Pattern

Extending Existing Modules

// Base module
const StringUtils = (function() {
  return {
    trim: (str) => str.trim(),
    toLowerCase: (str) => str.toLowerCase(),
    toUpperCase: (str) => str.toUpperCase()
  };
})();

// Augment the module without modifying original
...

Augmentation allows extending modules without modifying original code. Create new features externally.

Common Module Pattern Mistakes

❌ Exposing Private Variables

Mistake: Accidentally Exposing Internals

// WRONG - Exposes internals
const BadModule = (function() {
  let secretData = 'secret';
  let internalState = {};
  
  return {
    secret: secretData, // Exposes private variable!
    state: internalState, // Reference to internal object!
  };
})();
...

Only expose methods, not variables. Return copies or read-only access to prevent external modification.

❌ Memory Leaks from Closures

Mistake: Holding References Too Long

// PROBLEM - Closure holds large data even when unused
const Module = (function() {
  let largeArray = new Array(1000000).fill('data'); // Large object
  
  return {
    getValue: function() {
      return 42; // Doesn't use largeArray, but it's still held!
    },
    
    cleanup: function() {
...

Closures capture their entire scope. Provide cleanup methods to free unused variables.

❌ Overcomplicating Simple Code

Mistake: Using Modules for Simple Functions

// OVERCOMPLICATION - Simple function doesn't need Module Pattern
const MathModule = (function() {
  return {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
  };
})();

// BETTER - Keep it simple for simple cases
const add = (a, b) => a + b;
...

Use Module Pattern when you need encapsulation. For simple utilities, keep it straightforward.

Module Pattern Best Practices

  • Use IIFE to create module scope immediately
  • Keep private variables and functions hidden - return only what's needed
  • Use Revealing Module Pattern for clarity - define all functions, reveal selectively
  • Accept dependencies as parameters for testability
  • Provide cleanup methods if module holds large resources
  • Use for complex state or when standard functions don't suffice

Module Pattern vs ES6 Modules

FeatureModule PatternES6 Modules
SyntaxIIFE (function)import/export keywords
EncapsulationClosuresLanguage level
Static AnalysisNot possibleFull support
Tree ShakingNot supportedFull support
Browser SupportAll versionsModern browsers

Related Topics

Frequently Asked Questions

When should I use the Module Pattern?

Use it when you need data privacy, complex state management, or organizing code into namespaces. For simple utilities, ES6 modules are often better.

What's the difference between Module Pattern and ES6 modules?

Module Pattern uses closures and IIFEs. ES6 modules use native language features with import/export, better tree-shaking, and static analysis support.

Can I modify private variables from outside a module?

No, truly private variables cannot be accessed externally. Only public methods can access and modify them. This is the power of the Module Pattern.

How do I test private functions?

Private functions are tested indirectly through the public API. If you need direct testing, consider exposing them or using a different architecture.

What about memory leaks with Module Pattern?

Modules hold their entire closure scope. If they reference large objects, that memory cannot be garbage collected. Provide cleanup methods when needed.

Can modules have multiple instances?

Yes. By default, modules are singletons. To create multiple instances, wrap the module in a factory function.