Implementing the Revealing Module Pattern JS

The revealing module pattern organizes JavaScript code into clean, encapsulated modules with explicit public APIs. Learn how it works, why it matters, and how to use it today.

7 min read

The revealing module pattern is a way to structure JavaScript code so that internal details stay hidden and only a chosen public API is exposed. It uses closures to create private scope and a return statement to "reveal" which functions and variables are public.

The name describes exactly what happens: you define everything inside a function, then reveal only the parts you want the outside world to use.

The Problem: No Built-in Privacy

JavaScript did not always have private class fields or module-level scope. Before ES6 modules, everything was global unless you wrapped it in a function:

javascriptjavascript
// Without a module pattern -- everything is global
let count = 0;
 
function increment() {
  count += 1;
  updateDisplay();
}
 
function updateDisplay() {
  console.log(count);
}
 
// Any code can do this:
count = 999; // Oops -- direct mutation of internal state

The revealing module pattern fixes this by wrapping state and functions inside an IIFE so count and updateDisplay are not reachable from outside.

The Pattern Step by Step

Step 1: Wrap in an IIFE

Wrap your code in an immediately invoked function expression. This creates a private scope:

javascriptjavascript
const Counter = (function () {
  // Everything here is private
  let count = 0;
 
  function increment() {
    count += 1;
  }
 
  function getCount() {
    return count;
  }
 
  // Step 2: reveal the public API
  return {
    increment,
    getCount
  };
})();
 
console.log(Counter.getCount()); // 0
Counter.increment();
console.log(Counter.getCount()); // 1
console.log(Counter.count);      // undefined -- private

The critical line is the return statement. It is a map that says: "the outside world can call increment and getCount, but count stays private." This is where the "revealing" name comes from.

Revealing module pattern: private scope vs public API

Everything lives in the private scope. The return statement creates pointers from public names to private implementations. External code interacts only with the public API. count is unreachable.

Step 2: Name Internal Functions Clearly

The pattern is easiest to read when internal function names match their public names:

javascriptjavascript
const UserStore = (function () {
  const users = [];
 
  function addUser(user) {
    users.push(user);
  }
 
  function getUsers() {
    return [...users]; // Return a copy so callers cannot mutate internal array
  }
 
  function removeUser(id) {
    const index = users.findIndex(u => u.id === id);
    if (index !== -1) users.splice(index, 1);
  }
 
  return {
    addUser,
    getUsers,
    removeUser
  };
})();

Notice the return statement is just a clean list of names. No function definitions. No logic. The return says "these are public" and nothing else.

Step 3: Keep the Return Statement Clean

A good revealing module has a dead-simple return statement:

javascriptjavascript
return {
  addUser,
  getUsers,
  removeUser
};

A bad revealing module has logic in the return statement:

javascriptjavascript
// Avoid this
return {
  addUser: function(user) { /* ... */ },
  getUsers: function() { /* ... */ }
};

The first version is the whole point of the pattern. Define everything at the top of the IIFE. Reveal at the bottom. This makes it easy to scan the return statement and know exactly what the module exposes.

The Pattern with ES Modules

If you are using ES modules, you do not need an IIFE. The module file itself is the private boundary:

javascriptjavascript
// counter.js
let count = 0; // Private -- not exported
 
export function increment() {
  count += 1;
}
 
export function getCount() {
  return count;
}

Everything not exported is private. The export keyword replaces the return statement as the "reveal" mechanism:

javascriptjavascript
// userStore.js
const users = []; // Private
 
export function addUser(user) {
  users.push(user);
}
 
export function getUsers() {
  return [...users];
}
 
export function removeUser(id) {
  const index = users.findIndex(u => u.id === id);
  if (index !== -1) users.splice(index, 1);
}

The principle is identical: define everything at module scope, export only the public API. The difference is that import and export replace the IIFE wrapper and the return statement.

ES modules are the modern way to do this. Learn more about how they work in the ES modules import export guide.

Practical Use: Form Validator Module

Here is a realistic module built with the pattern:

javascriptjavascript
const FormValidator = (function () {
  const rules = {
    required(value) {
      return value !== undefined && value !== null && value !== "";
    },
    email(value) {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    },
    minLength(min) {
      return (value) => value.length >= min;
    }
  };
 
  function validateField(value, ruleList) {
    const errors = [];
 
    ruleList.forEach(rule => {
      if (typeof rule === "string") {
        const validatorFn = rules[rule];
        if (validatorFn && !validatorFn(value)) {
          errors.push(rule);
        }
      } else if (typeof rule === "object") {
        const [name, param] = Object.entries(rule)[0];
        const validatorFn = rules[name];
        if (validatorFn && !validatorFn(param)(value)) {
          errors.push(name);
        }
      }
    });
 
    return errors;
  }
 
  function validate(formData, schema) {
    const result = { valid: true, errors: {} };
 
    Object.keys(schema).forEach(field => {
      const fieldErrors = validateField(formData[field], schema[field]);
      if (fieldErrors.length > 0) {
        result.valid = false;
        result.errors[field] = fieldErrors;
      }
    });
 
    return result;
  }
 
  function addRule(name, fn) {
    rules[name] = fn;
  }
 
  return {
    validate,
    addRule
  };
})();

Usage:

javascriptjavascript
const result = FormValidator.validate(
  { username: "", email: "bad" },
  {
    username: ["required"],
    email: ["required", "email"]
  }
);
 
console.log(result);
// { valid: false, errors: { username: ["required"], email: ["email"] } }

The rules object and the validateField helper are private. Only validate and addRule are exposed. Callers get a clean two-method API with all the complexity hidden inside.

Common Mistake: Exposing Mutable References

When you return a reference to a private object or array, callers can mutate it:

javascriptjavascript
const Store = (function () {
  const items = ["a", "b"];
 
  return {
    getItems() {
      return items; // Wrong -- returns the actual array
    }
  };
})();
 
const data = Store.getItems();
data.push("malicious");
console.log(Store.getItems()); // ["a", "b", "malicious"] -- internal state mutated

Always return a copy for arrays and objects:

javascriptjavascript
getItems() {
  return [...items];
}

When to Use the Revealing Module Pattern

Use it when:

  • You need a singleton with private state and a clean public API.
  • You are writing a library and want to hide internal helpers from consumers.
  • You are organizing code in a script file that does not use a build system.

Skip it when:

  • You are already using ES modules with import/export. The module file itself provides the same boundary.
  • You need multiple instances of the module. Use a factory function instead.
  • You are working in a framework that has its own module or component system.
Rune AI

Rune AI

Key Insights

  • The revealing module pattern uses closures to create private state.
  • All logic is defined first, then only the public members are returned.
  • The return statement acts as a map of public names to private implementations.
  • With ES modules, the pattern is simpler: just do not export what should be private.
  • It is the foundation for understanding encapsulation in larger JavaScript architectures.
RunePowered by Rune AI

Frequently Asked Questions

Is the revealing module pattern still relevant with ES modules?

Yes, for different reasons. ES modules provide file-level encapsulation. The revealing module pattern provides function-level encapsulation inside a module when you need runtime private state that persists across calls.

What is the difference between the module pattern and the revealing module pattern?

The classic module pattern defines public members directly on the returned object. The revealing module pattern defines everything as private first, then returns an object that selectively exposes (reveals) which members are public.

Does the revealing module pattern work without IIFEs?

Yes. An ES module file acts as a natural module boundary. You can place the private logic at the top of the file and export only the public API at the bottom.

Conclusion

The revealing module pattern is a clean way to organize code with a clear public API and truly private internals. Its core idea, define everything first, then reveal only what callers need, applies whether you use IIFEs or ES modules.The revealing module pattern is the simplest way to achieve encapsulation in JavaScript. Wrap your logic in a function (or a module file), define everything in private scope, and return (or export) only what callers should use. The return statement is the contract between your module and the outside world. Keep it clean. Keep it short. Everything else stays private. This pattern is the foundation that modern module systems are built on.