The JavaScript Module Pattern

Before ES6 brought native modules to JavaScript, developers needed a way to encapsulate code, hide private data, and expose only a clean public API. The Module Pattern solved all three problems using closures and immediately invoked function expressions (IIFEs). Even in a world of import and export, understanding this pattern deepens your knowledge of closures and encapsulation.

The Problem It Solves

Without encapsulation, all your variables and functions live in the global scope, where they can accidentally collide with third-party libraries or other parts of your codebase. The Module Pattern gives you:

  • Private state — internal variables that can't be accessed from outside.
  • A public API — a controlled surface of functions and values you choose to expose.
  • Namespace protection — everything lives inside one object, not polluting the global scope.

Basic Structure

The pattern wraps your code in an IIFE and returns an object containing the public-facing methods:

const Counter = (() => {
  // Private state — not accessible from outside
  let count = 0;

  // Private helper
  function validate(value) {
    return typeof value === "number";
  }

  // Public API
  return {
    increment() {
      count++;
    },
    decrement() {
      count--;
    },
    add(value) {
      if (validate(value)) count += value;
    },
    getCount() {
      return count;
    },
  };
})();

Counter.increment();
Counter.increment();
Counter.add(5);
console.log(Counter.getCount()); // 7
console.log(Counter.count);      // undefined — count is private!

Revealing Module Pattern

A popular variation is the Revealing Module Pattern. Instead of defining methods directly on the returned object, you define everything privately and then reveal only what you want:

const ShoppingCart = (() => {
  let items = [];

  function addItem(item) {
    items.push(item);
  }

  function removeItem(name) {
    items = items.filter(i => i.name !== name);
  }

  function getTotal() {
    return items.reduce((sum, i) => sum + i.price, 0);
  }

  function getItems() {
    return [...items]; // Return a copy to protect internal state
  }

  // Reveal only these methods
  return { addItem, removeItem, getTotal, getItems };
})();

ShoppingCart.addItem({ name: "Book", price: 12.99 });
ShoppingCart.addItem({ name: "Pen", price: 1.50 });
console.log(ShoppingCart.getTotal()); // 14.49

The key advantage here is readability — you can scan the return statement at the bottom to instantly see what's public.

Module Pattern vs ES6 Modules

FeatureModule Pattern (IIFE)ES6 Modules
Private stateVia closuresVia unexported variables
Public APIReturned objectexport keyword
File separationSingle file/scopeOne module per file
Browser supportAll browsersModern browsers + bundlers
Static analysisLimitedExcellent (tree-shaking)

When to Use the Module Pattern Today

Modern projects typically use ES6 modules. However, the Module Pattern is still valuable when:

  • You're writing a self-contained script without a build step.
  • You're maintaining legacy code that predates ES6 modules.
  • You need to understand closures at a deep level — this pattern is a perfect teaching tool.

Key Takeaways

The Module Pattern remains one of the most important patterns in JavaScript history. It demonstrates the power of closures to create stateful, encapsulated objects — a concept that underpins everything from React hooks to class-based OOP. Study it, understand it, and you'll write better JavaScript regardless of which modern tool you reach for next.