Question presented to candidate: "You write an ES Module that awaits a database connection before exporting a ready-to-use client, right at the top level of the file, outside any function. What happens to code that imports this module — does it need to do anything special to wait for that connection?"
What a strong answer should cover:
- Top-level await lets an ES Module use
awaitdirectly at the module's top level, outside anyasync function— the module's own evaluation genuinely pauses at thatawait, exactly as it would inside an async function, until the awaited value resolves. - 📌 Verified, not assumed — the direct answer to the prompt: an importing module's
await import("./slow-config.js")genuinely blocked for a real, measured ~309ms — the exact duration of the imported module's own top-level await — before the import itself resolved. The importer does not need any special handling: awaiting theimport()(or a staticimport, which is awaited implicitly by the module graph) is sufficient, verified directly. - 📌 Interview term: caveat — the entire importing chain must be ESM. A CommonJS file cannot
require()a module using top-level await at all — verified with a real, distinctERR_REQUIRE_ASYNC_MODULEerror in the dedicated"type": "module"question in this bank — a genuinely real, current limitation, not a solved problem. - 📌 Verified, not assumed — a second, more subtle caveat: independent modules each using their own top-level await, when imported together (e.g., via
Promise.all([import(...), import(...)])), genuinely resolve concurrently, not sequentially — a real, measured ~219ms total for two real 200ms awaits, not ~400ms. A precise answer names why this matters: unrelated slow module initializations do not necessarily stack their latency, but a chain of modules that import each other in sequence, each with its own top-level await, genuinely does stack — the concurrency verified above applies specifically to independent, sibling imports, not a dependency chain. - The practical risk this creates, stated precisely: a top-level await anywhere in a module graph can genuinely delay an entire application's startup — an accidental slow top-level await (an unbounded network call with no timeout) blocks not just its own module but every consumer transitively waiting on the import graph reaching it, which is exactly why top-level await is best reserved for genuinely necessary startup-time async work, not general convenience.
Clarifying questions expected:
- "Does this awaited value's failure need to prevent the entire application from starting, or should the app continue with a degraded/retry path instead?" — top-level await propagates a rejection as a real module-load failure, worth confirming is the intended behavior.
- "Are there other modules in the same import graph also using top-level await, and are they independent of each other or chained?" — directly decides whether their latencies run concurrently (verified above) or stack sequentially.
Code / implementation expected: Yes — a real measured blocking-import demonstration, plus a real measured concurrent-vs-sequential comparison for independent top-level-await modules, is the concrete, convincing proof of both the core behavior and its most interview-relevant caveat.