QuestionsJavaScript

Dynamic import() and top-level await

Modules / Async LoadingMediumJavaScript

Contrast static import with dynamic import(), and explain top-level await and its trade-offs.

What it tests

Code-splitting mechanics, conditional/lazy loading, and how top-level await affects a module graph.

Approach & answer

Static `import ... from` is hoisted, resolved before execution, and must sit at the top level — that static shape is what lets bundlers build the dependency graph and tree-shake. Dynamic `import(specifier)` is a function-like operator that returns a PROMISE for the module namespace object; it can appear anywhere (inside conditionals, event handlers, functions) and the specifier can be computed at runtime. Bundlers turn each dynamic import into a separate CHUNK, so it's the primary mechanism for code-splitting and lazy loading — load a heavy editor/chart library only when the feature is actually used, shrinking the initial bundle. You consume it with await or .then, and destructure named exports off the namespace (`const { render } = await import('./chart.js')`). Top-level await lets an ES MODULE use `await` at module scope (no wrapping async function) — handy for modules that must resolve async config, a DB connection, or a dynamically chosen dependency before exporting. The trade-off: a module with top-level await becomes async, so every importer WAITS for it to finish evaluating before their own code runs — it can serialise and slow the module graph's startup, and it only works in ESM (not CommonJS). Use it for genuine module-initialisation needs, not as a convenience.

Use this technique when

Route-based/feature code-splitting, conditionally loading polyfills or heavy libs, and module-level async initialisation.

References

js