cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Why modules?

Learn why JavaScript moved from shared global scripts to IIFEs, CommonJS, AMD, and ES modules: private scope, explicit imports and exports, safer load order, and reusable files.

By the end, you'll be able to
  • 01
    Spot global collisionsExplain why classic scripts share names and why order matters.
  • 02
    Compare module patternsSeparate IIFEs, namespaces, CommonJS, AMD, and ES modules by their tradeoffs.
  • 03
    Read modern modulesRecognize import/export as explicit file boundaries with private top-level scope.

Why modules exist

As programs grow, code stops living in one tiny file. A shopping cart has price math, a checkout screen, form validation, analytics, tests, and little helpers. If every file drops its names into the same place, the program becomes a crowded room where anyone can accidentally overwrite anyone else.

A module is JavaScript with a boundary: private names inside, and explicit doors for anything another file may use. Modern JavaScript modules use export for the front desk and import for the visitor list. Older code used IIFEs, namespaces, CommonJS, and AMD to solve parts of the same problem before ES modules became the standard.

Real-life analogyFrom one shared whiteboard to separate offices

Imagine every team in a company writing important numbers on one whiteboard. The cart team writes count = 1. The profile team arrives later and writes count = 99. Nobody broke a wall; they simply reused a shared label. Modules give each team its own office, then make sharing deliberate.

In real life: One shared whiteboard in an open-plan office
In JavaScript: Classic global scope: every script can write names like count
In real life: A private meeting room with one labeled cubby
In JavaScript: IIFE plus namespace: private helpers, one public object
In real life: Separate offices with a front desk and visitor list
In JavaScript: Modules: export says what leaves, import says what enters

Where the analogy stops: Offices have people and doors you can see. JavaScript modules are rules enforced by the loader and the language; exported objects can still contain mutable values if you choose to share them.

The goal

Modules make code easier to reason about because a file’s public surface is visible. If a name is not exported, other files cannot reach it directly. If a name is not imported, the file should not depend on it.

Problems with global scope

INTERACTIVE

Classic browser scripts are simple: place several <script> tags on the page, and they run in order. The catch is that they share one global script scope. Top-level var and top-level function declarations become properties of the global object in browsers, so window.count and window.format appear. Top-level let, const, and class are still global lexical names, but they do not become window properties. The old var and the global object lesson digs into that distinction.

The collision lab runs real classic scripts inside a sandboxed iframe. Toggle the order and watch which feature owns the shared names.

Global collision lab
Two classic scripts choosing the same namesPop out in the code editor (opens in a new tab)HTML
<script>var count = 1;function format() { return "cart:" + count; }window.cartFeature = format();</script><script>var count = 99;function format() { return "profile:" + count; }window.profileFeature = format();</script><script>window.report = [cartFeature, profileFeature, format(), window.count];</script>
Sandbox resultscart-first
cartFeaturecart:1
profileFeatureprofile:99
format()profile:99

The last script’s function wins.

window.count99

Top-level var leaked to the global object.

Try it yourself

The sandbox ran real classic scripts. With this order, profile owns the shared names, and window.count is 99.

The iframe uses sandbox='allow-scripts' and predefined srcdoc. The parent accepts messages only from this frame.

This kind of bug is sneaky because each file can look reasonable by itself. The cart file says “I need a count and a format.” The profile file says the same thing. The page only breaks when both are loaded together, and the order decides which definition survives.

IIFE and namespace patterns

INTERACTIVE

Before standard modules, developers often wrapped a feature in an IIFE: an immediately invoked function expression. A function creates function scope, and calling it right away runs the setup. Anything declared inside stays private unless the function returns it or assigns it somewhere public.

Real-life analogyA private meeting room with one cubby

An IIFE is like borrowing a small room for setup. You can spread papers across the table without cluttering the public whiteboard. When you leave, you put only one folder in a labeled cubby for other people to use.

In real life: Book a meeting room
In JavaScript: Run a function immediately: (function () { ... }())
In real life: Notes on the room’s private table
In JavaScript: Local var, let, const, and helper functions
In real life: One labeled cubby on the office whiteboard
In JavaScript: A namespace object such as window.CartApp

Where the analogy stops: The meeting room keeps helpers private, but the cubby label is still on the shared whiteboard. Two teams could both choose window.App, and script order is still manual.

IIFE and namespace fix
Wrap each feature and expose one namespacePop out in the code editor (opens in a new tab)HTML
<script>window.CartApp = (function () {  var count = 1;  function format() { return "cart:" + count; }  return { feature: format() };}());</script><script>window.ProfileApp = (function () {  var count = 99;  function format() { return "profile:" + count; }  return { feature: format() };}());</script>
Sandbox resultsprivate helpers
CartApp.featurecart:1
ProfileApp.featureprofile:99
window.countundefined
window.formatundefined
public namesCartApp, ProfileApp
Try it yourself

The two features still share deliberate public names, but their helper variables and functions now live inside private function scopes.

This pattern works in old browsers because it is just functions and objects. It is a stepping stone, not the final module system.

The namespace pattern was a huge improvement over loose globals. It made intent visible: “the public cart API is CartApp.” But it still relied on global namespace objects and careful script order. The next patterns add loaders.

CommonJS and AMD

STEP THROUGH

CommonJS and AMD are two older module systems you still meet in tools, libraries, and historical code. CommonJS uses synchronous require(id) and module.exports. It was Node’s original module system: perfect when files are on disk and can load immediately. The important mechanics are a wrapper function, an exports object, and a cache after the first load.

Step through a tiny CommonJS require
Step 0 of 11Ready
Your turn: follow the blue line

Step through a tiny CommonJS-style loader. Switch between the first load and the cached second load.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  "./math": function (module) {    module.exports.add = (a, b) => a + b;  },  "./cart": function (module, exports, require) {    const math = require("./math");    exports.total = (items) => items.reduce(math.add, 0);  }};const cache = {};function require(id) {  if (cache[id]) return cache[id].exports;  const module = { exports: {} };  cache[id] = module;  modules[id](module, module.exports, require);  return module.exports;}const cart = require("./cart");console.log(cart.total([2, 3]));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the call to replay.

Changing the setting swaps the last lines of the displayed model and starts a fresh replay.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

AMD, often associated with RequireJS, took a different path for browsers. Its shape is define(["dep"], factory): list dependencies, load them asynchronously, then run the factory. You do not need to write AMD for modern browser apps, but recognizing it helps you read older packages.

AMD in brief: async dependency loading
AMD shapeJavaScript
define(["./math"], function (math) {  return {    total: function (items) {      return items.reduce(math.add, 0);    }  };});
Loader timelinemodel
  1. request ./math
  2. continue page
  3. math loaded
  4. run cart factory
  5. cart ready: 5
Step 0 of 5predict first

Predict what must happen before the factory can run.

This is a small model of AMD's loading idea, not RequireJS itself. No external library is downloaded.

ES modules

REAL BROWSER

ES modules are the standard JavaScript module system, standardized in ES2015 and now supported by browsers and Node. A module has its own top-level scope, is always strict, runs once per URL, and shares values only through export and import. In browsers, <script type="module"> is deferred by default, so it waits until parsing is complete instead of blocking the parser like a normal classic script.

ES modules in a real browser iframe
Module script shape inside the iframeHTML
<script type="importmap">{ "imports": { "cart": "data:text/javascript,..." } }</script><script type="module">import { count, total } from "cart";window.moduleTop = typeof count;console.log(total([2, 3]));console.log(this === undefined);</script>
Module resultsreal browser
total([2, 3])waiting for module
top-level this undefined…
window.count…
module internal leak check…
Try it yourself

The module imports from a predefined data URL through an import map. Notice window.count is undefined even though count was imported and used.

Sandboxed iframe, predefined source, and a source check on messages. Browser module scripts are deferred by default; this tiny frame posts after the module runs.

Notice the shift in thinking: modules do not ask, “What names are already on window?” They ask, “Which file exports the thing I need?” That is why the next lessons can teach named exports, default exports, re-exports, import maps, dynamic import, and CommonJS interop as precise tools rather than folklore.

Global script, IIFE, CommonJS, AMD, or ES module?

The patterns have recognizable fingerprints. Sort each snippet by the system it belongs to. If you miss one, read the explanation and look for the clue: window, an immediately called function, require, define, or import/export.

Module pattern comparison
PatternSharing styleMain tradeoff
Classic globalsEveryone writes to one shared global scopeSimple, but collisions and load-order bugs are easy
IIFE + namespacePrivate local names, one public objectWorks anywhere, but dependencies are still manual globals
CommonJSrequire returns module.exports synchronouslyGreat for servers/tools; browser use historically needed bundling
AMDdefine([...], factory) waits for dependenciesBrowser-friendly async loading, but verbose
ES modulesStatic import and exportStandard now; you must learn loader rules and file URLs
Sort global or IIFE patterns
  • var count = 0; function format() { return count; }
  • window.CartApp = (function () { var count = 0; return { count }; }());
  • Two <script> tags both declare function init()
  • (() => { const secret = 1; window.App = { start() {} }; })();
Try it yourself
0 of 4 correct

First separate loose globals from the IIFE namespace workaround.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Sort module loader patterns
  • module.exports = { total }; const tax = require("./tax");
  • define(["dep"], function (dep) { return {}; });
  • import { total } from "./cart.js"; export { total };
  • const cart = require("./cart"); const again = require("./cart");
  • define(["./cart"], function (cart) { cart.start(); });
  • <script type="module" src="app.js"></script>
Try it yourself
0 of 6 correct

Now sort the module-system snippets: CommonJS, AMD, or ES module.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Where you’ll use this

In real front-end work, modules let you split by responsibility. One file can own cart math. Another can render the checkout. Tests can import the math without rendering the page. A future teammate can see the dependency by reading the first line of the file.

A small practical module splitJavaScript
// cart.jsexport function total(items) {  return items.reduce((sum, item) => sum + item.price, 0);} // checkout.jsimport { total } from "./cart.js"; export function renderCheckout(items) {  return "Total: $" + total(items);}
  • Use modules when a helper has a clear job and a small public surface.
  • Keep private helpers unexported until another file genuinely needs them.
  • Prefer explicit imports over “I hope this global already exists.”
  • When reading legacy code, identify which pattern owns loading and sharing before changing names.

Common misconceptions

“A separate file is automatically a module.”

No. A browser treats a script as a module only when you use type="module" or when another module imports it. Node has its own package and extension rules.

“IIFEs and ES modules are the same thing.”

Both create privacy, but IIFEs are a function pattern. ES modules have a standard loader, static imports and exports, strict mode, and run-once-per-URL behavior.

“Modules mean no shared state.”

Modules stop accidental top-level leakage. If you export a mutable object, other modules can still mutate that object. Boundaries are tools, not magic.

“CommonJS is just old ES module syntax.”

CommonJS loads synchronously with require and writes to module.exports. ES modules use static syntax and different loader rules.

“AMD, CommonJS, and ES modules are bundlers.”

They are module formats or systems. Bundlers are tools that read modules and produce files optimized for delivery.

Honest shortcut

In modern browser-focused code, reach for ES modules first. Learn CommonJS and AMD so you can read existing packages, debug build output, and understand why the ecosystem has interop rules.

Practice: make the boundary visible

5 EXERCISES
Exercise 1 · Warm-upPredict the global collision

What are the two printed lines?

Starter codePop out in the code editor (opens in a new tab)JavaScript
var count = 1;
function format() { return "cart:" + count; }
var count = 99;
function format() { return "profile:" + count; }
console.log(format());
console.log(globalThis.count);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeName the namespace fix

    What two public names remain on window?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    window.CartApp = (function () {
      var count = 1;
      return { feature: "cart:" + count };
    }());
    window.ProfileApp = (function () {
      var count = 99;
      return { feature: "profile:" + count };
    }());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeCommonJS cache check

      Predict what the comparison prints.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const cache = {};
      function requireCart() {
        if (cache.cart) return cache.cart;
        cache.cart = { loadedAt: 1 };
        return cache.cart;
      }
      console.log(requireCart() === requireCart());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeStrict like a module

        What does this module-like strict-mode proof print?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        "use strict";
        function moduleLike() {
          return this === undefined;
        }
        console.log(moduleLike());

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePlan a module refactor

          A checkout page has price math, DOM rendering, and analytics in one classic script. Write down three modules you would split out, then choose one function each should export.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Why do classic script files collide so easily?

              Choose an answer to see the explanation.

            2. Question 2 of 7Which output shows the later classic function winning?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              var count = 1;
              function format() { return "cart:" + count; }
              var count = 99;
              function format() { return "profile:" + count; }
              console.log(format());
              console.log(globalThis.count);

              Choose an answer to see the explanation.

            3. Question 3 of 7What does an IIFE mainly protect?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does CommonJS do on the second require of the same ID?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const cache = {};
              function requireCart() {
                if (cache.cart) return cache.cart;
                cache.cart = { loadedAt: 1 };
                return cache.cart;
              }
              console.log(requireCart() === requireCart());

              Choose an answer to see the explanation.

            5. Question 5 of 7Which description matches AMD?

              Choose an answer to see the explanation.

            6. Question 6 of 7What is special about browser ES modules?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does this strict-mode proof print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              function moduleLike() {
                return this === undefined;
              }
              console.log(moduleLike());

              Choose an answer to see the explanation.

            Key takeaways

            • Classic scripts share global names; top-level var and function declarations can appear on window.
            • IIFEs create private function scope and usually expose one namespace object.
            • CommonJS uses synchronous require, module.exports, and a cache.
            • AMD uses define([deps], factory) for asynchronous dependency loading.
            • ES modules have private top-level scope, are strict, run once per URL, and share through import/export.

            Remember the one-liner.
            A module is a file with a boundary: private by default, public only through its exports.

            Up next: import & export, the syntax for named exports, default exports, re-exports, and live bindings.

            CompleteFrontend Clear concepts. Working examples.