In an ES module `main.js` you write `console.log('main start');` and then, on the line below it, `import './dep.js';` — and `dep.js` logs `'dep evaluated'` at its top level. Which line prints first, and what rule decides that?
answer
- order is not line order
- declarations, then linking, then running
- dependencies finish before importers start
- setup above an import runs too late
basics
~20 sThe dependency prints first. Import declarations are hoisted and the whole module graph is linked before anything is evaluated, so dep.js runs to completion before any statement in main.js — including a statement written above the import.
solid answer
~50 s`dep evaluated` prints first, then `main start`. An `import` declaration is not a statement that runs where you wrote it: the engine parses the module, discovers every import before executing a single line, and evaluates the dependency graph depth-first, so every dependency's body finishes before the importing module's body begins. Position in the file only decides the order *among* the imports — when a module has several, their bodies are evaluated in declaration order. The practical consequence is that you cannot set something up above an import and expect the imported module to see it: the classic bug is assigning `globalThis.CONFIG` before an `import './uses-config.js'` line and finding `CONFIG` undefined inside that module. If ordering matters, express it as a real dependency — pass the value in, or have the dependency import the setup module itself.
go deeper
Be ready to state the order out loud: the imported module's top-level code runs before the importing module's, no matter where the import line sits. Knowing that one rule answers most output-order questions about modules.
Explain the mechanism, not just the outcome: the graph is parsed and linked before evaluation, then evaluated depth-first, with sibling dependencies in declaration order. Show you can predict the output for a three- or four-module graph.
Show you recognise the bug shape in review — top-level setup written above an import, or correctness that leans on a particular import order. Explain how you convert implicit line ordering into a real dependency edge or an explicit initialisation call.
Own the design consequence: module bodies that do work at import time make evaluation order part of your contract, which is invisible in code review and fragile across refactors. Argue for declaration-only module bodies plus an explicit composition root as the default.
## The answer `dep evaluated` prints first, then `main start`. Nothing about the physical position of the `import` line changes this. ## Why: modules are linked before they are run Loading an ES module happens in distinct phases. First the engine **parses** the entry module and, purely from its syntax, reads off the list of modules it depends on; it fetches and parses those, recursively, until the whole graph is known. Then it **links** the graph, creating each module's bindings and wiring every import to the export it refers to. Only then does **evaluation** begin — the phase in which top-level code actually runs. Because the graph is fully known before any code runs, the engine can evaluate it depth-first: to evaluate a module, first evaluate each of its dependencies (in the order their `import` declarations appear), then run the module's own body. A module's body therefore always runs *after* the bodies of everything it imports. ## Hoisting `import` declarations are hoisted to the top of the module in the same sense that they take effect before the body runs. Two observable consequences: ```js // main.js console.log('main start'); // runs second import { greet } from './dep.js'; // dep.js body already ran console.log(greet()); // works ``` First, code written physically above the import still runs after the dependency. Second, an imported binding is usable in code that appears above its `import` line — the binding was created during linking and, in a non-cyclic graph, already holds a value because the exporting module was evaluated first. (This is unlike `let`/`const`, which are unusable before their declaration in the same module body.) ## Ordering among several imports With multiple dependencies, declaration order decides: ```js // main.js import './a.js'; // a's body runs first import './b.js'; // then b's console.log('main'); // then this ``` Depth-first means a nested dependency goes even earlier: if `a.js` itself imports `shared.js`, the output is `shared`, `a`, `b`, `main`. And a module that several modules import is evaluated only once — later importers reuse the already-evaluated instance. ## The bug this causes The most common real-world trip-up is trying to configure something before importing the module that reads it: ```js // main.js — broken globalThis.API_BASE = 'https://example.com'; import './client.js'; // client.js reads API_BASE at its top level -> undefined ``` `client.js` is evaluated before the assignment ever runs, so it sees `undefined`. The same shape shows up with polyfills, logger setup, and feature flags. Fixes, in rough order of preference: 1. **Make the dependency real.** Put the setup in its own module and have `client.js` import it; now the graph, not line order, guarantees the ordering. 2. **Export a function instead of doing work at import time.** `client.js` exports `createClient(config)`, and `main.js` calls it after setting up. Module bodies that only declare things are immune to ordering questions entirely. 3. **Load on demand.** Where the decision genuinely has to happen at runtime, a module can be brought in later rather than as a static dependency. ## How to reason about it in an interview Say the rule in one line — "imports are hoisted, the graph is linked before evaluation, and dependencies are evaluated depth-first in declaration order" — then show that you know what it costs you: any correctness that depends on top-level statements running before an imported module is a bug waiting to happen, because the import always wins. One caveat worth naming: in a circular graph a module can be evaluated while a dependency is only partway through, so an imported binding can still be uninitialized. That is the exception that proves the rule — the ordering guarantee is exactly "dependencies first" for graphs without cycles.
- If `main.js` imports `a.js` and then `b.js`, and `a.js` itself imports `shared.js`, what is the evaluation order of the four bodies?`shared.js`, then `a.js`, then `b.js`, then `main.js`. Evaluation is depth-first over the graph, following the order of the `import` declarations in each module: to evaluate `a.js` the engine must first evaluate everything `a.js` imports. `shared.js` runs exactly once even if `b.js` imports it too — the second importer reuses the already-evaluated instance.
- Can code written above an `import` line call a function that the import brings in?Yes, in a non-cyclic graph. The binding is created during linking, before any evaluation, and the exporting module has already been evaluated by the time the importing body runs, so the function is available anywhere in the body regardless of where the `import` line sits. The exception is a circular graph, where the exporting module may not have finished, and touching the binding can throw.
- You need a polyfill to be installed before any other module's top-level code runs. How do you guarantee that with static imports alone?Put the polyfill import first in the entry module and make sure nothing it must precede is imported earlier in that file — declaration order decides among siblings. The more robust version is to make the dependency explicit: the modules that need the polyfill import it themselves, so depth-first evaluation guarantees it regardless of what the entry file looks like after the next refactor.
saying these in an interview costs you the question
- Says import statements run in the order they appear in the file
- Claims the importing module's top-level code runs first
- Thinks moving the import lower delays when the dependency runs
- Assumes a value assigned above an import is visible to it
- Says imports behave like require and execute inline