Performanceintermediate
Code Splitting and Lazy Loading Modules
Breaking a JavaScript bundle into smaller chunks loaded on demand, using dynamic import() to reduce the initial page load's JavaScript payload.
Code splitting breaks a bundle into chunks loaded on demand. import() is the mechanism.
Code splitting is like a restaurant that doesn't print and hand every guest its entire encyclopedic menu including seasonal items, off-menu specials, and the dessert list all at once — it hands you the core menu immediately so you can start ordering right away, and only brings out the dessert menu (a separate chunk) later, specifically once you actually ask for it.
Key Concepts
1
// loaded at startup
import Chart from './chart.js';2
// loaded when this line runs
const { default: Chart } = await import('./chart.js');3
The bundler sees the dynamic import and emits a separate chunk.
4
Why it matters. Every kilobyte in the initial bundle must be downloaded, parsed and executed before the
page is interactive. Parsing is the part people forget — it is CPU work, and on a mid-range phone it
dominates.
5
A dashboard shipping a charting library to users who never open the chart pays that cost on every visit.
6
The three places to split.By route. The largest win, and the easiest:
7
const Settings = lazy(() => import('./Settings'));8
By interaction. A modal, an editor, a map — loaded when opened.
9
button.onclick = async () => {
const { openEditor } = await import('./editor.js');
openEditor();
};10
Heavy libraries. A date library, a PDF generator, a syntax highlighter — imported inside the function
that needs them.
11
Preloading removes the delay. Splitting trades bundle size for a wait at the moment of use. Fetching the
chunk on hover, or during idle time, gets both:
12
link.onmouseenter = () => import('./Settings');13
Do not split too finely. Each chunk is a request with its own overhead, and a page pulling in twenty
small chunks can be slower than one reasonable bundle over a high-latency connection.
14
Handle the failure. A chunk request can fail — a flaky network, or a deploy that removed the old file:
15
import('./chart.js').catch(() => showFallback());16
That second case is real: a user with the page open during a deploy requests a chunk that no longer exists,
and the feature silently does nothing without a handler.
17
Measure before splitting. webpack-bundle-analyzer, or Vite's rollup-plugin-visualizer, shows what is
actually large. The answer is usually one or two dependencies rather than the application code, and the fix
is often replacing a library rather than splitting it.
webpack-bundle-analyzerrollup-plugin-visualizer
18
What the interviewer is probing.1. "What does dynamic import give you?" Probing: the mechanism. Stalls: "Async loading."
Moves up: a promise for the module and a separate bundle chunk, so the code is not in the initial
download.
19
2. "Where should you split?" Probing: judgement. Stalls: "Everywhere." Moves up: at route
boundaries and around heavy rarely-used features; splitting too finely adds request overhead.
20
3. "What does parse cost mean on mobile?" Probing: the real constraint. Stalls: "Download
size." Moves up: JavaScript must be parsed and executed as well as downloaded, and on a mid-range
phone that is often the dominant cost.
21
4. "How do you avoid the loading delay?" Probing: prefetching. Stalls: "You cannot." *Moves
up:* prefetch the chunk during idle time so it is cached before the user navigates.