In a single-page app, two lazily loaded route chunks each import the same heavy date-formatting library. What are the two ways a bundler can handle that shared dependency, and how do you decide between them?
answer
- one module needed by two lazy chunks
- copy into both, or hoist it out
- size threshold decides
- two copies means two module instances
- extra file means extra request
basics
~20 sEither the library is duplicated into both route chunks, or it is extracted into a shared chunk both routes load. Duplication costs repeat bytes for users who visit both routes; extraction costs an extra request per route. Decide by module size and how often routes are visited together.
solid answer
~50 sA bundler can copy the shared module into each route chunk, or hoist it into a third chunk that both routes depend on and fetch alongside their own. Most modern bundlers extract by default, because duplication means a user who visits both routes downloads, parses, and executes the same library twice — and, worse, ends up with two independent module instances, so any module-level state or singleton registry inside it exists twice. Extraction fixes that but adds a request on the critical path of each route and makes each file smaller, which compresses slightly worse. The decision is a size-versus-count judgment: extract when the shared module is big enough that duplicating it is visible, keep duplication when it is a few kilobytes of helpers and an extra round trip costs more than the repeated bytes. If both routes are almost always visited in one session, the shared chunk is nearly free; if they serve disjoint audiences, extraction just adds a hop for everyone.
code
javascript · 11 lines// heavy-date-lib keeps module-level state
let locale = 'en-US';
export function setLocale(next) { locale = next; }
export function formatRange(a, b) {
const f = new Intl.DateTimeFormat(locale);
return `${f.format(a)} - ${f.format(b)}`;
}
// If this module is duplicated into two route chunks, setLocale() called
// from the reports route does NOT affect the copy inside the billing route:
// they are two separate module instances with two separate `locale` bindings.go deeper
Know that when two lazily loaded routes need the same library, the build either copies it into both files or puts it in a separate shared file that both load, and that the copy version means downloading it twice.
Explain the size-versus-request-count tradeoff and the module-instance consequence of duplication: two copies are two independent module scopes, so module-level state is not shared between them.
Show that you pick a threshold deliberately using how large the module is and how often the routes are co-visited, and that you check whether the shared chunk is fetched in parallel with its parent rather than after it.
Own the policy across the app: a small number of deliberate shared chunks with a real size floor, guarded against both extremes — utility duplication inflating total bytes, and chunk explosion turning every navigation into a chatty dependency resolution.
## The situation ```js // routes/reports.js import { formatRange } from 'heavy-date-lib'; // routes/billing.js import { formatRange } from 'heavy-date-lib'; ``` Both route modules are reached only through dynamic imports, so each becomes its own chunk. `heavy-date-lib` is needed by both. The bundler must decide where its bytes live. ## Option 1 — duplicate Copy the library into `reports.[hash].js` and again into `billing.[hash].js`. *Upside:* each route is one self-contained request. Navigating to reports costs exactly one fetch, with no dependency graph to resolve first. Fewer files overall means better compression per file and a simpler loading story. *Downside:* a user who visits both routes pays for the library twice — twice the transfer, twice the parse, twice the execute. And there is a correctness dimension people forget: two copies are two **module instances**. Anything module-scoped inside the library — a plugin registry, a configured locale, a cache, a counter — now exists twice, and configuring it on one route has no effect on the other. That class of bug is maddening to diagnose because the code looks like it is talking to one object. ## Option 2 — extract a shared chunk Hoist the library into `shared.[hash].js`. Both route chunks declare a dependency on it, and the loader fetches the shared chunk before (or in parallel with) the route chunk. *Upside:* the bytes exist once, are parsed once, and are one module instance. On the second route, the shared chunk is already in memory, so that navigation is cheaper than the first. Cache-wise it is also better across deploys — editing the reports route does not invalidate the library. *Downside:* more files. Each route load now resolves two or three URLs instead of one, and if the loader discovers the shared chunk only after parsing the route chunk, that is an extra serialized round trip rather than a parallel one. Small files also compress worse than large ones, so ten 4 KB chunks total more bytes than one 40 KB chunk. ## How to decide Three inputs, in order: **1. How big is the shared module?** This dominates. Duplicating 3 KB of helpers is noise; duplicating a 90 KB library is a real regression for anyone who visits both routes. A common heuristic is a minimum size threshold — below it, duplicate; above it, extract. Bundlers expose such a threshold precisely because the answer is size-dependent. **2. How many chunks share it, and how often are they visited together?** A module shared by two rarely co-visited routes is a weak extraction candidate: most users pay the extra request and never benefit from the reuse. A module shared by eight route chunks, or by routes that appear in the same flow, is a strong one. **3. Does it hold state?** If the module owns a singleton — a client instance, a registry, a configuration object — duplication is not merely wasteful, it is a bug. Extract regardless of size. ## The failure mode at each extreme Turn extraction off entirely and popular utilities get copied into every route chunk; the bundle report shows the same package attributed to a dozen chunks and total shipped bytes balloon. Turn it up to "extract everything shared by two or more chunks" and you get chunk explosion: dozens of tiny files, each a request, many discovered only after their parent parses. Navigation gets slower even though every individual file is small — you have traded one big download for a deep, chatty dependency graph. The healthy middle is a handful of deliberate shared chunks with a real size floor, plus tolerance for small duplicates. ## Making the extraction cheap If you extract, make sure the shared chunk downloads **in parallel** with the route chunk rather than after it. Build tools that know a dynamic import's static dependency graph can emit preload hints for the child chunks at the moment the parent is requested, which collapses two sequential fetches into one round trip. Without that, a shared chunk can be slower than the duplication it replaced.
- Why does duplicating a module sometimes cause a bug rather than just waste bytes?Because ES module state is per module instance, not per specifier. Two copies mean two independent sets of module-level variables, so a registry, a cache, or a configured client initialised on one route is invisible to the other. Code that assumes a singleton silently gets two, and the symptom — settings that do not stick across navigation — looks nothing like a bundling problem.
- How would you spot that a library is being duplicated across chunks?Look at the build's stats output for the same package attributed to more than one chunk, and compare the sum of chunk sizes against what you expect the app to contain. At runtime, a module that logs or registers on first evaluation firing twice is a strong signal. Multiple installed versions of the same package cause the same symptom and need resolving in the dependency tree instead.
- When does extracting a shared chunk actually make navigation slower?When the shared chunk is discovered only after the route chunk has been downloaded and parsed, turning one round trip into two. For a small shared module that is a bad trade. The fix is to make the child chunk discoverable at request time — emitting a preload hint for it when the route is requested — or to leave it duplicated.
saying these in an interview costs you the question
- Assumes shared code is automatically deduplicated with no cost
- Thinks two copies of a module still share module-level state
- Extracts every module shared by two chunks regardless of size
- Ignores the extra request a shared chunk adds to each route
- Treats chunk count as free because files are small