Lesson 65 of 80
A static import runs at load time, whether you need the module yet or not. import() runs on demand, returns a promise, and is the mechanism every bundler's code-splitting is built on.
13 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A regular import Chart from "chart.js" at the top of a file is static — it has to be resolved before any of the module's code runs, so Chart ships in the initial bundle whether the user ever opens the chart screen or not. import(), the function form, is different: it's a call you make at runtime, wherever you like, and it returns a promise that resolves to the module. Nothing gets fetched until that call actually happens.
That one difference is the entire mechanism behind "code-splitting." Bundlers like webpack, Vite, and Next.js's own compiler see a dynamic import() and automatically carve the imported module into its own chunk, loaded lazily instead of bundled up front.
async function openChartPanel() {
const button = document.querySelector("#show-chart");
button.disabled = true;
button.textContent = "Loading…";
const { renderChart } = await import("./chart-renderer.js");
renderChart(document.querySelector("#chart-container"));
button.disabled = false;
}
document.querySelector("#show-chart").addEventListener("click", openChartPanel);
chart-renderer.js — and whatever heavy charting library it pulls in — never gets fetched or parsed until someone actually clicks the button. If they never click it, that code never costs the user anything. Compare that to a static import { renderChart } from "./chart-renderer.js" at the top of the file: it would load on every page visit, whether the chart is ever shown or not.
import() also accepts an expression, not just a string literal — import(./locales/$.js) — which is how apps lazily load one of many possible modules (a locale, a route, a plugin) based on something only known at runtime.
import("./chart-renderer.js") from ten different places and the network request happens once; every call after the first resolves instantly from the cache.import() is a real ECMAScript feature that works in browsers with no bundler at all — but the "separate file, loaded on demand" behavior you associate with code-splitting is a bundler convention layered on top, not something the spec guarantees.import() loads code that was already known about at build time, just deferred. Module federation (a webpack/Vite concept) goes further — loading a module from a separate build, owned by a separate team or deploy, that your app didn't even know about when it was compiled. This lesson covers the language feature; federation is an architecture built on top of it.import() is just another promise-returning function — the same .then()/await handling you'd use for a fetch call works here unchanged. If promises already make sense, there's nothing new to learn about how to consume a dynamic import, only when reaching for one pays off.