JavaScript Interview Prep
Design Patterns

Module Pattern

The Vending Machine Blueprint for Private Scope

LinkedIn Hook

Every JavaScript file you wrote before 2015 had a silent enemy: the global scope.

Declare a variable at the top level, and congratulations — it is now visible to every script on the page, every library you did not write, and every bug you have not yet discovered.

The Module Pattern was the community's first serious answer. An IIFE wraps your logic, closure keeps the internals private, and a returned object becomes the public API. Vending machine in, vending machine out — you choose what the consumer can touch.

In this lesson you will build both the classic Module Pattern and the Revealing Module variant from scratch, compare them to modern ES modules, and learn exactly when IIFE modules still pull their weight in 2026.

Read the full lesson -> [link]

#JavaScript #ModulePattern #DesignPatterns #Closures #InterviewPrep #WebDevelopment #Frontend


Module Pattern thumbnail


What You'll Learn

  • How an IIFE plus closure creates a truly private scope inside plain JavaScript
  • The difference between the standard Module Pattern and the Revealing Module Pattern
  • When IIFE modules are still useful and when ES modules make them obsolete

The Vending Machine Analogy

Think of a module like a vending machine. You put in a coin and press a button — you get a product. But you never see the internal mechanics: the conveyor belt, the stock management, the coin-counting logic. The module pattern works the same way: it exposes a clean public API while keeping internal details completely hidden.

IIFE-Based Module Pattern

Before ES modules existed, JavaScript developers used IIFEs (Immediately Invoked Function Expressions) to create private scope:

// Classic Module Pattern using IIFE
const CounterModule = (function () {
  // Private variables — cannot be accessed from outside
  let count = 0;
  const MAX_COUNT = 100;

  // Private function
  function validateCount(value) {
    return value >= 0 && value <= MAX_COUNT;
  }

  // Public API — returned object
  return {
    increment() {
      if (validateCount(count + 1)) {
        count++;
      }
      return count;
    },
    decrement() {
      if (validateCount(count - 1)) {
        count--;
      }
      return count;
    },
    getCount() {
      return count;
    },
    reset() {
      count = 0;
      return count;
    }
  };
})();

CounterModule.increment(); // 1
CounterModule.increment(); // 2
CounterModule.getCount();  // 2

// Private members are truly inaccessible
console.log(CounterModule.count);         // undefined
console.log(CounterModule.MAX_COUNT);     // undefined
console.log(CounterModule.validateCount); // undefined

Revealing Module Pattern

A variation where you define all functions privately, then "reveal" only the ones you want public:

const UserService = (function () {
  // All functions defined as private
  let users = [];

  function addUser(name, email) {
    const user = {
      id: Date.now(),
      name,
      email,
      createdAt: new Date()
    };
    users.push(user);
    _logAction("add", user);
    return user;
  }

  function removeUser(id) {
    const index = users.findIndex(u => u.id === id);
    if (index === -1) return false;
    const removed = users.splice(index, 1)[0];
    _logAction("remove", removed);
    return true;
  }

  function getUsers() {
    // Return a copy to prevent external mutation
    return [...users];
  }

  function findUser(predicate) {
    return users.find(predicate);
  }

  function _logAction(action, user) {
    console.log(`[UserService] ${action}: ${user.name}`);
  }

  // Reveal only what should be public
  return {
    add: addUser,
    remove: removeUser,
    getAll: getUsers,
    find: findUser
    // _logAction is NOT revealed — stays private
  };
})();

UserService.add("Alice", "alice@example.com");
UserService.getAll(); // [{ id: ..., name: "Alice", ... }]

Module Pattern vs ES Modules

// IIFE Module (pre-ES6)
const MyModule = (function () {
  const secret = "hidden";
  return { getSecret: () => secret };
})();

// ES Module (modern)
// myModule.js
const secret = "hidden"; // private to module by default
export const getSecret = () => secret;

// consumer.js
import { getSecret } from "./myModule.js";
FeatureIIFE ModuleES Module
PrivacyClosure-basedFile-scoped by default
LoadingSynchronous (script tag)Async, static analysis
Tree-shakingNot possibleSupported by bundlers
Singleton by defaultYes (IIFE runs once)Yes (cached after first import)
Browser supportAll browsersModern browsers + bundlers

When to Use Module Pattern in Modern Code

  • Legacy codebases without a module bundler
  • Quick browser scripts where you cannot use import/export
  • Third-party library wrappers that need a clean namespace
  • Avoid when: you have ES modules or a bundler — just use import/export

Module Pattern visual 1


Common Mistakes

  • Mutating the array or object returned by the module from the outside — always return a copy ([...users]) or freeze the result if you want true immutability.
  • Forgetting that the IIFE runs exactly once — putting expensive setup inside makes the first script evaluation slow, even if the consumer never uses the module.
  • Reaching for an IIFE module when you already have ES modules and a bundler — you get nothing extra and lose tree-shaking.

Interview Questions

Q: What is the Module Pattern and why was it important before ES6?

The Module Pattern uses an IIFE to create a private scope, returning an object that serves as the public API. Before ES6 modules, JavaScript had no native way to create file-level privacy. Everything declared in a script was global. The Module Pattern solved this by using closure to simulate private members, preventing namespace pollution and accidental variable collisions.

Q: What is the difference between the Module Pattern and the Revealing Module Pattern?

In the standard Module Pattern, public functions are defined directly in the returned object. In the Revealing Module Pattern, ALL functions are defined privately first, and then the return statement "reveals" selected functions by mapping them to public names. The Revealing Module Pattern is more readable and makes it easier to see the full public API at a glance.

Q: Are ES modules singletons?

Yes. When you import a module, the JavaScript engine caches the module after first evaluation. Subsequent imports of the same module return the cached version. This makes ES modules natural singletons.


Quick Reference — Cheat Sheet

MODULE PATTERN — QUICK MAP

Shape:
  const M = (function () {
    // private scope (closure)
    return { /* public API */ };
  })();

Variants:
  - Classic        -> public methods defined inside returned object
  - Revealing      -> all private, return maps names -> private refs

Privacy:
  - IIFE module    -> closure-based, truly inaccessible
  - ES module      -> file-scoped by default, tree-shakable

Use when:
  - legacy browser script, no bundler
  - quick namespace for a third-party wrapper

Avoid when:
  - you already have ES modules / a bundler

Previous: Intersection & Mutation Observer -> Watching the DOM Next: Singleton Pattern -> The One-Instance Rule


This is Lesson 12.1 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.

On this page