Question presented to candidate: "Normally, await only works inside an async function. Top-level await lets you use it directly in an ES module body. Walk me through what actually happens to that module, and to any other module that imports it, while it is awaiting — and then tell me what can go genuinely wrong if you reach for this carelessly."
What a strong answer should cover:
- Top-level await lets a module's own body pause at an
awaitwithout wrapping it in an async function — but the module does not evaluate in isolation: any module that staticallyimports it (directly or transitively) genuinely waits for that top-level promise to settle before ITS OWN body runs. - A precise answer separates two different things: an UNRELATED sibling module (one that does not depend on the slow one) can still evaluate independently while the slow module awaits — only modules actually on the dependent chain are genuinely blocked.
- A subtle, easy-to-miss detail: import statements are hoisted for evaluation purposes — a module's own top-level code, even lines positioned textually BEFORE its import statements in source, genuinely does not run until every one of its static imports has finished evaluating.
- A rejected top-level await inside a STATICALLY imported module is a genuinely serious failure mode: it can crash the whole module graph's evaluation uncatchably from the importer's perspective, unlike a rejection reached through a dynamic
import(), which is a real, ordinary catchable promise rejection. - Circular imports combined with top-level await are a genuine, real risk — not just a theoretical one — capable of producing a real, live
ReferenceErrorfrom accessing a binding before its module has finished initializing. - A precise answer names when top-level await stabilized without a flag: Node.js v14.8.0 (ES modules only — it does not work in CommonJS).
Clarifying questions expected:
- "Is the slow operation at the top level something every consumer of this module genuinely needs before they can do anything (like a required config fetch), or could it be deferred to only where it is actually used?" — the real deciding factor for whether top-level await is the right tool versus a lazily-awaited function.
- "Could this module ever end up in a circular import relationship with something else in the dependency graph?" — a real, concrete risk category worth ruling out early.
Code / implementation expected: Yes — real, timestamped evidence of an importer genuinely blocking on a slow module's top-level await, plus a real reproduction of the circular-import failure mode, is the concrete way to prove the mechanics rather than just describe them.