Code Splitting

Code splitting divides an application into separately loaded JavaScript chunks to reduce the amount of code needed for the initial page load. The browser can fetch additional chunks when particular routes or features are needed.

A typical frontend build can produce one large JavaScript bundle containing code for many routes and features. Code splitting breaks that bundle into smaller chunks that can be requested independently, reducing the initial JavaScript download and parse work.

A common approach is dynamic `import()`. Unlike a static import, a dynamic import creates an asynchronous module boundary that bundlers can use to emit a separate chunk. The returned promise resolves to the requested module when its chunk has loaded.

Route-based splitting is especially common: code for a page can be loaded when the user navigates to that route. Feature-based splitting can similarly defer expensive functionality, such as an editor or charting library, until the user actually uses it.

Code splitting is not automatically a performance win in every situation. More chunks can introduce additional requests and loading boundaries, so the goal is to avoid shipping unnecessary code initially while keeping important code readily available. Modern bundlers can also optimize chunk loading and caching.

In an interview, distinguish code splitting from minification or tree shaking. Minification makes shipped code smaller, tree shaking removes unused exports when possible, while code splitting changes when and how portions of the code are delivered.

Key points

Interview questions

How does dynamic `import()` differ from a normal import?

A normal static import declares a module dependency that is part of the regular module graph. A dynamic `import()` returns a promise and loads the module asynchronously. Bundlers can use that asynchronous boundary to produce a separate chunk. This lets an application defer loading that module until it is needed.

What is code splitting and why would you use it?

Code splitting divides application code into multiple chunks that can be loaded independently. This can reduce the JavaScript required for the initial page. A common use case is loading route-specific code only when the user visits that route. The goal is to defer code that is not immediately needed.

What is route-based code splitting?

Route-based code splitting assigns separate chunks to different application routes. The initial route can load without downloading the JavaScript needed exclusively by other pages. When the user navigates, the application requests the relevant route chunk. This is particularly useful when different routes contain substantially different functionality.

What is the trade-off of creating many code-split chunks?

Splitting can reduce the amount of JavaScript needed initially, but every additional loading boundary can introduce network and scheduling overhead. Very small chunks may therefore provide little benefit or even hurt performance. Shared dependencies also need careful chunking so that code is not unnecessarily duplicated. The appropriate granularity depends on application structure, caching, network conditions, and runtime behavior.

How would you decide what code should be split?

I would first identify code that is not needed for the initial user experience, such as infrequently visited routes or optional heavy features. I would then consider dependency size, usage frequency, caching behavior, and whether the split introduces a noticeable loading delay. Performance measurements such as bundle analysis and browser loading metrics should guide the decision rather than splitting everything by default. Critical code should remain readily available when delaying it would harm the user experience.