Lesson 15 · 15 of 20
The module pattern and IIFEs · JavaScript
Hide internal state in JavaScript with IIFEs and the module pattern, and see how Node wraps each file and what module.exports exports.

An IIFE (immediately invoked function expression) is a function that is defined and run in the same breath: (function () { ... })(). The outer parentheses make it an expression and the final pair calls it. Everything declared inside, like items and log, stays in its private scope.
The module pattern combines an IIFE with closures: it returns an object holding only what you want to make public (add and count), and those functions still have access to items. From outside, inventory.items is undefined: the inventory can only be changed through its interface. Before JavaScript had modules, this is how web page code was organised.
Node does something very similar with every CommonJS file: it wraps it in a function with five parameters (exports, require, module, __filename and __dirname). That is why arguments.length is 5 at the top level of the file, and why its variables never mix with those of other files.
What a file exports is `module.exports`; exports is just a shortcut that initially points to the same object. If you assign a new object to module.exports, as the example does, exports still points to the old one and becomes useless: use exports.name = ... to add and module.exports = ... to replace, never exports = .... On top of that, require() caches every module, so two calls return the very same object.
Code · main.js
// IIFE: a function that runs at once and keeps its variables private.const inventory = (function () { const items = new Map(); // nothing outside can reach this const log = (msg) => console.log(`[inventory] ${msg}`); return { add(name, qty) { items.set(name, (items.get(name) ?? 0) + qty); log(`+${qty} ${name}`); }, count: (name) => items.get(name) ?? 0, };})();inventory.add("pens", 20);inventory.add("pens", 5);console.log("pens:", inventory.count("pens"), "| inventory.items:", inventory.items);// Node wraps every CommonJS file in a function just like that one:// (function (exports, require, module, __filename, __dirname) { ... })console.log("Wrapper arguments:", arguments.length, "|", typeof require, typeof module);console.log("exports === module.exports:", exports === module.exports);// Whatever ends up in module.exports is what require() returns.module.exports = { inventory, version: "1.2.0" };console.log("Exported:", Object.keys(module.exports).join(", "));console.log("exports still points to the old object:", exports !== module.exports);// require() caches modules: a second call returns the same object.const first = require("path");const second = require("path");console.log("Cached:", first === second, "| extname:", first.extname("orders.json"));Output · node main.js
[inventory] +20 pens [inventory] +5 pens pens: 25 | inventory.items: undefined Wrapper arguments: 5 | function object exports === module.exports: true Exported: inventory, version exports still points to the old object: true Cached: true | extname: .json
Key points
- An IIFE runs as soon as it is defined and creates a private scope.
- The module pattern exposes an interface and hides state in a closure.
- Node wraps every CommonJS file in a function.
- What gets exported is
module.exports;exportsis only an initial reference. require()caches modules.
Exercise
Add a remove(name, qty) method to the inventory that refuses to leave negative stock, and a private operation counter that can be read with an operations() method.