completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Load, link, evaluate

Learn how JavaScript loads a module graph, links live imports to exports, and evaluates each module in dependency order.

By the end, you can
  • 01
    Name the three stepsSay what loading, linking, and evaluation each do to a group of module files.
  • 02
    Follow a live importPredict when an imported value changes because the file that exports it changes it.
  • 03
    Predict the run orderWork out which file runs first in a small module graph, without running it.

Three steps: load, link, evaluate

A module is a JavaScript file that shares values with export and uses values from other files with import. The browser does not run a module the moment it sees it. First it gets every file ready. Only then does any code run.

The program that runs your JavaScript is called the host. It is the browser, or Node on a server.

Definition

Loading finds and reads every file the first file needs. Linking connects each import to the export it asks for. Evaluation runs the code in each file, imported files first. The three steps happen for the whole group of files, not one line at a time.

Three cards in a row joined by arrows. 1. load: find and read every file. 2. link: connect each import to its export. 3. evaluate: run the code, imported files first.1. load2. link3. evaluatefind and readevery fileconnect each importto its exportrun the code,imported files first
Every module goes through these three steps, in this order. No module code runs until step 3.

This lesson comes after the browser security model. Those browser rules decide whether a file may be downloaded at all. The steps in this lesson start after that. Node uses the same three steps, but it has its own rules for finding files.

Two more words you will see all through this lesson:

  • The module graph is the map of which file imports which.
  • The entry module is the file you start from, such as main.js.

Loading starts at the entry module. But the files it imports usually run before it does. That explains many surprising logs in real apps.

The record for one module

When the engine reads a module file, it writes down notes about it. The spec, the official rule book for JavaScript, calls these notes a Source Text Module Record. In this lesson we just say module record. A module record keeps:

  • which files this module imports from,
  • which names it imports,
  • which names it exports,
  • which step it has reached: unlinked, linking, linked, evaluating, or evaluated.
export let count = 0; export function add() {  count += 1;}

Line 1 exports a variable named count. It starts at 0. Line 3 exports a function named add. The engine can list both exports just by reading the file. It does not need to run add to learn its name.

import { count, add } from "./cart.js"; console.log(count);add();console.log(count);

Line 1 asks ./cart.js for two of its exports: count and add. Lines 3 to 5 are ordinary code, so they wait until every file is ready. Then they print 0 and then 1.

Your code can't see a module record. It is just a useful name. It helps you keep two things apart: what a module contains, and running its code. For example:

  • A typing mistake in the file, called a syntax error, stops the record from being made at all.
  • Importing a name that the other file doesn't export stops linking.

Step 1: loading the files

Loading means finding, downloading, and reading every module the entry module needs. Reading a file to understand its structure is called parsing. Start from main.js. The host sees that it asks for ./cart.js. It works out the full address of that file, downloads it if needed, and parses it into another module record.

A tiny entry modulePop out in the code editor (opens in a new tab)JavaScript
import { count } from "./cart.js";console.log(count);

Line 1 is enough to add cart.js to the graph. Line 2 does not run while files are still being found. It runs later, in step 3, after linking has connected count to the real export.

Real-life analogyPlanning a group trip

Before a group trip, you collect everyone's phone number. Then you make one WhatsApp group, so everyone can reach the right person. Only then does the trip start. Modules work the same way: collect, connect, then go.

In real life: Collect every friend's phone number
In JavaScript: Load every module the entry needs
In real life: Make one WhatsApp group
In JavaScript: Link each import to its export
In real life: Start the trip
In JavaScript: Run the code in each module
In real life: Friends get ready before the group leaves
In JavaScript: Imported files run before the files that import them

Loading repeats itself. Each new file can import more files, and those get loaded too. If two files import the same file, the host loads it only once. Each module gets one record in the graph, so a shared file is linked once and run once.

Step 2: linking imports to exports

Linking connects each import to the export it asks for. In main.js, the imported name count gets connected to the exported count in cart.js. A name connected to a value like this is called a binding. Linking does not copy the number into a new variable.

An import asks for one export by namePop out in the code editor (opens in a new tab)JavaScript
import { count } from "./cart.js"; console.log(count);

Line 1 asks for an export named exactly count. Line 3 reads it. If cart.js had no export called count, linking would fail with an error. You would not quietly get undefined.

This early error is useful. A file can be read without problems and still fail to link, because an import name is wrong. When linking fails, no code in the graph runs at all. You never get half a program.

Each row of this table is one step. Read across to see what the step does and what hasn't happened yet.

The three steps, side by side
StepWhat it doesWhat hasn't happened yet
LoadFinds, downloads, and reads every module file the entry module needs.The imports are not connected to exports yet. No module code has run.
LinkConnects each imported name to the export it asks for.The names are connected, but no module code has run yet.
EvaluateRuns the code in each module, imported files first.Nothing. This is the last step: exports get their values and logs print.

An import is not a copy

An imported name is a live binding. It always reads the value the exporting file has right now. That is different from copying a number into a new variable. The exporting file owns the variable. Files that import it can read it, but they can't give it a new value.

main.js reads count before and after add()Pop out in the code editor (opens in a new tab)JavaScript
import { count, add } from "./cart.js"; console.log(count);add();console.log(count);

Line 3 prints 0, because cart.js set count to 0. Line 4 calls add, a function inside cart.js. That function changes the same count. So line 5 prints 1, not an old copy of 0.

After add() on line 4. A name tag main.js count points to a box called count that holds 1. A function card add() points into the same box. The box belongs to cart.js.after add() on line 4main.jscountmain.js reads it1countcart.js owns itadd()changes it
The count in main.js points at the box in cart.js; it is not a copy. So after add() puts 1 in the box, line 5 reads 1.

The step-through below uses a small stand-in for cart.js: a function called createCartModule that keeps count in a private variable. Reading cart.count calls a getter, a small function that hands back the current count. The stand-in shows the same change, one step at a time.

Step through one live import
Step 0 of 5Ready
Your turn: follow the blue line

This replays a stand-in for a live import, one step at a time. It does not pause the real JavaScript engine.

Running in
  1. script
Next: line 1
 console.log(cart.count);cart.add();console.log(cart.count);
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
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.

The step-through replays the stand-in code. It does not pause the real JavaScript engine.

Step 3: which file runs first

Evaluation runs the code in each module. A module runs only after the modules it imports have run. So imported files run before the files that import them.

Four files that log when they runJavaScript
// shared.jsconsole.log("shared"); // a.jsimport "./shared.js";console.log("a"); // b.jsconsole.log("b"); // main.jsimport "./a.js";import "./b.js";console.log("main");

Here main.js imports a.js and b.js, and a.js imports shared.js. The logs come out as shared, a, b, and main. Each file finishes before the file that imports it logs anything.

A graph of four files. main.js imports a.js and b.js. a.js imports shared.js. The run order is shared.js first, then a.js, then b.js, and main.js last.main.jsruns 4tha.jsruns 2ndb.jsruns 3rdshared.jsruns 1st
An arrow means "imports". A file runs only after the files it imports, so shared.js runs first and main.js runs last.

You can work out this order by hand with three rules:

  1. Start at the entry module.
  2. Before you write down a module, go into each of its imports, in order, and write those down first.
  3. Never write down the same module twice.

Programmers call this a depth-first, post-order walk. Depth-first means you follow one import all the way down before you start the next one. Post-order means a module is written down after its imports.

A small program that finds the run orderPop out in the code editor (opens in a new tab)JavaScript
const graph = {  "main.js": ["a.js", "b.js"],  "a.js": ["shared.js"],  "b.js": [],  "shared.js": [],}; function evaluationOrder(graph, entry) {  const visited = new Set();  const order = [];  function visit(name) {    if (visited.has(name)) return;    visited.add(name);    for (const dependency of graph[name] ?? []) visit(dependency);    order.push(name);  }  visit(entry);  return order;} console.log(evaluationOrder(graph, "main.js").join(", "));

This program follows those rules. It uses a Set, a list that holds each value at most once.

  • Line 9 makes the visited set, which remembers the modules already seen.
  • Line 12 stops if a module was seen before.
  • Line 14 visits every import first.
  • Only then does line 15 add the current module to order.
  • Line 21 prints shared.js, a.js, b.js, main.js.

This is a small model for learning, not the engine's real code.

Step through the run order
Step 0 of 6Ready
Your turn: follow the blue line

This replays a small run-order program, one step at a time. A real host also loads and links every file before any of them run.

Running in
  1. script
Next: line 1
  "main.js": ["a.js", "b.js"],  "a.js": ["shared.js"],  "b.js": [],  "shared.js": [],}; function evaluationOrder(graph, entry) {  const visited = new Set();  const order = [];  function visit(name) {    if (visited.has(name)) return;    visited.add(name);    for (const dependency of graph[name] ?? []) visit(dependency);    order.push(name);  }  visit(entry);  return order;} console.log(evaluationOrder(graph, "main.js").join(", "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
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.

Change one import

Change one import and watch the order change. In the playground, remove only the import from a.js to shared.js. Now nothing imports shared.js, so main.js can't reach it. It drops out of the run order.

Playground: change one import
The run-order programPop out in the code editor (opens in a new tab)JavaScript
const graph = {  "main.js": ["a.js", "b.js"],  "a.js": ["shared.js"],  "b.js": [],  "shared.js": [],}; function evaluationOrder(graph, entry) {  const visited = new Set();  const order = [];  function visit(name) {    if (visited.has(name)) return;    visited.add(name);    for (const dependency of graph[name] ?? []) visit(dependency);    order.push(name);  }  visit(entry);  return order;} console.log(evaluationOrder(graph, "main.js").join(", "));
What the program finds4 modules
a.js importsshared.js

Changing this one import changes which modules main.js can reach.

run ordershared.js -> a.js -> b.js -> main.js

Imported files come before the files that import them. Each module that main.js can reach appears once.

Try it yourself

a.js imports shared.js, so shared.js runs before a.js. main.js still waits for both a.js and b.js.

This uses the lesson's small evaluationOrder program to work out the run order. It is not a real browser loading modules.

Reset puts the import back. The rule to remember: not every file in a folder runs. Only modules that the entry module can reach through imports are loaded and run.

One object with every export

import * as cart from "./cart.js" gives you one object, cart, that holds every export of cart.js. This object is called a module namespace object. Its values are live, like any import. But the importing file can't give them new values.

main.js gets every export as one objectPop out in the code editor (opens in a new tab)JavaScript
// cart.js exports count and add.import * as cart from "./cart.js"; console.log(Object.keys(cart).join(","));console.log(cart[Symbol.toStringTag]);console.log(cart.count);cart.add();console.log(cart.count);
  • Line 2 makes the object named cart.
  • Line 4 lists its property names with Object.keys. It prints add,count, because the names come out in alphabetical order.
  • Line 5 reads a special built-in key, Symbol.toStringTag, which holds a short type name. Here it prints Module.
  • Lines 6 to 8 show the live value: count goes from 0 to 1.
Left: import * as cart gives a cart that holds count and add(). Reading cart.count works and gives 0, then 1. Right: writing cart.count = 2 is refused with a TypeError.import * as cartcountadd()cartread: 0, then 1cart.count = 2TypeErrorwrite refusedonly cart.js changes count
import * as cart gives you one object with every export in it. You can read cart.count, but only cart.js can change it.

Module code always runs in strict mode, JavaScript's stricter set of rules. In strict mode, cart.count = 2 throws a TypeError, the error for using a value the wrong way. This protects the exporting file. cart.js can still change its own let count. Other files can only read it.

Each row of this table is a way to reach values. Read across to see what you get and when to use it.

Named import, module object, or plain object?
What you useWhat you getWhen to use it
A named import, like import { count }One live name, such as count.Use it when you know which exports you need.
A module object, from import * as cartOne read-only object that holds every export. Its values are live.Use it to look at all the exports, or to keep them together under one name.
A plain object, like { count: 0 }An ordinary object. You can give its properties new values.Use it for your own data. It is not a module object.

How this helps in real apps

A feature file should import only the shared values it needs. A checkout page can import add and count from a cart module. It doesn't need to know how the cart keeps its data: in a simple variable, in a store, or on a server.

A small feature modulePop out in the code editor (opens in a new tab)JavaScript
import { add, count } from "./cart.js"; add();console.log(count);

Line 1 imports the two exports by name. Line 3 changes the cart through its own add function. Line 4 reads the current count. If the cart starts at 0, as in this lesson, it prints 1.

The three steps also help you find bugs. Ask which step failed:

  • A wrong file address or a network problem happens during loading.
  • An error about a missing export happens during linking.
  • An error thrown by code at the top level of a file happens during evaluation.

Knowing the step tells you where to look.

Load, link, or evaluate?
  • Download cart.js because main.js imports it.
  • Read cart.js and note that it exports count and add.
  • Connect the imported count to the count that cart.js exports.
  • Get the cart module object ready for import * as cart.
  • Run console.log("shared") in shared.js.
  • Run add() so the exported count becomes 1.
Try it yourself
0 of 6 correct

Put each event in the step where it happens. Read the explanation after each card.

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

Common module mistakes

  • “An import gets a copy.” An imported name is a live binding. It reads the exporting file's current value.
  • “Loading runs code.” Loading only finds and reads the files. The code runs later, during evaluation.
  • “Imports run top to bottom, like the lines in my file.” Imported files run before the files that import them. The module graph decides the order.
  • “import * as gives a plain object.” It gives a special module object. You can read its live values, but you can't assign to them.
  • “Each import runs a shared file again.” A file runs only once in a graph, no matter how many files import it.

Each row of this table is a word from this lesson. Read across to see what it means and what it does not mean.

Similar words, different jobs
WordWhat it meansWhat it does not mean
LoadingFinding, downloading, and reading every file in the module graph.Running the code in those files.
LinkingConnecting each imported name to the export with the same name.Copying exported values into new variables.
EvaluationRunning a module's code after the modules it imports have run.Running files in the order the import lines are written.
Module namespace objectA special read-only object with every export of one module.A plain object that the importing file can change.

These differences matter even more in the next lesson. There, two modules import each other. That is called a cycle. To follow a cycle, you need to know exactly which names are linked before either file finishes running.

Practice exercises

Exercise 1 · Warm-upPredict a live value

Read the small program. Type the two lines it prints, in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
let count = 0;
function add() { count += 1; }
console.log(count);
add();
console.log(count);

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

    Exercise 2 · Warm-upName the step that connects names

    A teammate asks: which step connects an import to an export? Type the name of the step.

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

      Exercise 3 · PracticePredict the run order

      Follow the small program by hand. Type what it prints, with the names separated by commas.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const graph = { main: ["a", "b"], a: ["shared"], b: [], shared: [] };
      const visited = new Set();
      const order = [];
      function visit(name) {
        if (visited.has(name)) return;
        visited.add(name);
        for (const dependency of graph[name]) visit(dependency);
        order.push(name);
      }
      visit("main");
      console.log(order.join(","));

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

        Exercise 4 · PracticeRead the module object's tag

        What text does a module namespace object hold at the key Symbol.toStringTag?

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

          Exercise 5 · PracticePredict a write to cart.count

          In a module, cart came from import * as cart. Then the code runs cart.count = 2. What happens?

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

            Exercise 6 · ChallengeUse the cart module in a feature

            Your product page needs to add an item and show how many items are in the cart. What should it import from a shared cart module?

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

              Check your understanding

              For each question, first name the step: load, link, or evaluate. Trace the module graph only when a question asks about order. For a question about an imported value, ask two things: which file owns the value, and which file only reads it.

              Load, link, evaluate quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What is a module record, in plain words?

                Choose an answer to see the explanation.

              2. Question 2 of 7This small program acts like the live import. What does it print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                let count = 0;
                function add() { count += 1; }
                console.log(count);
                add();
                console.log(count);

                Choose an answer to see the explanation.

              3. Question 3 of 7Which step connects import { count } to the export named count?

                Choose an answer to see the explanation.

              4. Question 4 of 7This program works out the run order. What does it print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const graph = { main: ["a", "b"], a: ["shared"], b: [], shared: [] };
                const seen = new Set();
                const order = [];
                function visit(name) { if (seen.has(name)) return; seen.add(name); for (const child of graph[name]) visit(child); order.push(name); }
                visit("main");
                console.log(order.join(","));

                Choose an answer to see the explanation.

              5. Question 5 of 7For a real module namespace object, what is cart[Symbol.toStringTag]?

                Choose an answer to see the explanation.

              6. Question 6 of 7In a module, can cart.count = 2 give an export a new value?

                Choose an answer to see the explanation.

              7. Question 7 of 7Why does the lesson's run-order program keep a visited set?

                Choose an answer to see the explanation.

              Key takeaways

              • A module record is the engine's notes about one module file: its imports, its exports, and which step it has reached.
              • Loading finds and reads every file the entry module needs. Linking connects each import to its export. Evaluation runs the code.
              • An imported name is a live binding. When the exporting file changes the value, every file that imports it reads the new value.
              • Imported files run before the files that import them. Each module runs only once.
              • import * as gives a module namespace object. You can read its live values, but you can't assign to them. Its tag is Module.
              • When something goes wrong, first ask which step failed: loading, linking, or evaluation.

              In one line.
              The host loads every module, links every import, and only then runs the code, imported files first.

              Next lesson: circular dependencies, where two modules import each other. You will use the same module records, live bindings, and run order to see what happens.

              CompleteFrontend Clear concepts. Working examples.