skip to content

A module counts how many times its top-level code runs and five different modules import it. How many times does the body actually run, what identifies a module instance, and how can a codebase end up with two separate instances of what looks like the same module?

level: seniorimportance: should knowfreq 42%

answer

  1. once per instance, not once per import
  2. identity comes from resolution, not the text you typed
  3. two copies on disk, two singletons
  4. same source, different key
  5. a worker has its own everything

basics

~20 s

Once. A module's body is evaluated exactly one time per instance, and instances are keyed by the resolved location of the module, so all five importers share one instance. Two instances appear when the same source resolves to two different locations — a duplicated package copy, a differing specifier, or a separate realm.

solid answer

~50 s

The body runs once. During loading the engine keeps a registry of modules keyed by *resolved* specifier — a URL in a browser, a resolved file URL in a server runtime — so the first import evaluates the module and every later importer, however deep in the graph, gets the same already-evaluated instance with the same bindings. That is what makes a module-scoped variable behave as a singleton. You get two instances when the resolved keys differ even though the source is the same: two physical copies of a package in the dependency tree, a specifier with a query string appended, the same file reached by two different paths, or a separate realm such as a worker, which has its own registry entirely. The symptom is a "singleton" that mysteriously has two states — a cache that misses, a registry missing entries, an `instanceof` check failing against a class from the other copy.

go deeper

for a junior

Know the basic rule: importing a module many times evaluates it only once, and every importer shares the same exported values. That is why module-level state acts like a shared object.

for a middle

Explain that instances are keyed by the resolved location, so the same source reached through two different resolved paths is genuinely two modules with two sets of state.

for a senior

Recognise the production symptoms — a half-empty cache, a registry missing entries, a failing instanceof — and drive them to a root cause with a resolved-URL log or the installed dependency tree, then fix by converging the duplicate copy.

for a principal

Treat implicit module singletons as a coupling decision, not a free service. Argue for explicit construction and injection at the entry point, and for dependency policies that keep shared packages single-copy across the organisation.

## One evaluation per instance ```js // counter.js let loads = 0; loads++; console.log('counter.js evaluated', loads); export const state = { hits: 0 }; ``` No matter how many modules import `counter.js`, that log appears once. During construction the engine records the module in a registry; the first time it is needed, it is fetched, parsed, linked, and evaluated, and every subsequent import of the same key reuses that record. The importers all share the same `state` object, so mutations by one are visible to all — the accidental-singleton property that module-scoped state relies on. Two consequences follow. First, top-level code is initialisation code that runs exactly once, in graph order, and never again — including on a later import of the same module. Second, if that code throws, the module is permanently marked as failed; it is not retried on the next import. ## What identifies an instance The registry key is the module's **resolved** location, not the specifier you typed. `./util.js` in one file and `../lib/util.js` in another, resolving to the same URL or file, are the same instance. Conversely, anything that changes the resolved key produces a different instance, and JavaScript makes no attempt to notice that the *contents* are identical. The host matters here in the way you'd expect but should be careful about: a browser keys its module map by URL; a server runtime keys by the resolved file URL after its own resolution rules run. Rather than asserting the fine print of a particular runtime, say the level-appropriate thing in an interview: *resolution happens first, and the resolved result is the identity*. ## How duplicates happen **Two copies in the dependency tree.** Two of your dependencies require incompatible versions of a shared package, so the installer keeps two physical copies at different paths. Both are legitimately resolved, both are evaluated, and the "singleton" inside that package now exists twice. **A specifier that resolves differently.** Appending a query string — `./m.js?v=2` — makes a distinct key against a browser's module map, and the module is fetched and evaluated again. (That is sometimes done on purpose to force re-evaluation; it is a duplicate instance either way.) **The same file reached by two paths.** Path aliasing, a symlinked local package, or an inconsistent mix of extensionless and extensioned specifiers can each yield two distinct resolved keys for one file on disk. **A separate realm.** A worker, or a separate frame, has its own registry and its own copy of everything, with no sharing of module state at all. This one is not a bug; it is the boundary working as designed, and code that assumes a shared module singleton across it is the bug. ## Symptoms and diagnosis The tells are familiar once you have seen them: a cache or connection pool that behaves as if it were empty for half the app; a plugin registry that has entries in one place and not another; a configuration object mutated at startup that some module reads as untouched; an `x instanceof Foo` check failing on an object that is visibly a `Foo`, because two copies of the class exist and identity is per-copy. To confirm, add a one-line, temporary side effect at the module's top level that logs `import.meta.url` — the module's own resolved URL, available inside any ES module. Two lines with different URLs is the whole diagnosis; two lines with the *same* URL would mean something else entirely (two realms, or two separate loads of the application). At the package-manager level, listing the installed tree shows whether a package appears at more than one path. ## Designing around it The robust answer is to reduce how much you depend on module identity. Prefer explicit wiring — create the shared object once at your entry point and pass it to the code that needs it — over relying on `import` to hand everyone the same thing. Where a package genuinely must be a singleton across a whole application, the standard mitigations are structural rather than clever: converge the dependency on one version so only one copy is installed, or declare it as a peer of the packages that share it so the top-level application owns the single copy. And notice what does *not* help: exporting a frozen object, using a class with a static instance, or a lazily-created instance behind a getter. Every one of those lives inside the module, and if the module is instantiated twice, so is it. The fix has to happen at the level that decides how the specifier resolves.

  • How would you confirm at runtime that a module has been instantiated twice?
    Temporarily log `import.meta.url` from the module's top level: since the body runs once per instance, two log lines with different URLs prove two instances and show you exactly which resolved paths are involved. At the package level, inspecting the installed dependency tree shows whether the package is physically present at more than one path.
  • Does exporting a lazily created instance behind a getter protect a singleton from duplication?
    No. The laziness lives inside the module, so if the module itself is instantiated twice you get two lazy slots and two instances. Any singleton implemented in module scope inherits the module's identity. The fix is structural — make the specifier resolve to one place, or create the object once at the entry point and pass it down explicitly.
  • Why can `instanceof` start failing when a package is duplicated?
    Each copy of the module evaluates its own class declaration, producing two distinct constructor functions with two distinct prototype objects. An object built by one copy does not have the other copy's prototype in its chain, so the check is false even though the source code is identical. It is the clearest symptom that identity is per-instance, not per-source.
  • Is module state shared with a worker started by the page?
    No. A worker runs in its own realm with its own module registry, so it evaluates its own copy of every module and shares none of their state. Anything they need in common has to be passed across the message boundary explicitly. Code that assumes an imported singleton spans that boundary is relying on a guarantee that never existed.

saying these in an interview costs you the question

  • Says the body runs once per importing module
  • Thinks identical file contents guarantee one instance
  • Believes freezing the exported object prevents duplication
  • Assumes module state is shared with workers
  • Claims a re-import re-runs the module's top-level code

context