In a microfrontend architecture where several independently-built apps are combined on one page, what is the difference between sharing a UI library like React as a 'singleton' versus letting each microfrontend bundle its own 'scoped' copy, and why does the choice matter?
answer
- one instance vs many copies
- React context needs one React
- coordination cost vs coupling cost
- module federation shared config
- invalid hook call = duplicate React
basics
~20 sSingleton means every microfrontend uses one shared copy of a library loaded once on the page. Scoped means each microfrontend ships and uses its own private copy. Singleton saves download size but couples apps; scoped isolates apps but repeats code.
solid answer
~40 sSingleton sharing designates one library (usually one with global/mutable state like React, a router, or a state manager) as 'there can only be one instance on the page.' The host or the module-federation runtime loads it once, and every microfrontend that declares it as shared reuses that instance instead of bundling its own. Scoped (non-shared, or 'isolated') sharing lets each microfrontend bundle and load its own copy, fully independent of what siblings use. Singleton is required for libraries that break with multiple instances -- React context, Redux stores, routing state -- because two React copies can't share hooks or contexts. Scoped is safer for stateless utility libraries (lodash, date formatting) where duplication only costs bytes, not correctness, and lets teams upgrade independently without coordinating a shared version.
go deeper
Should articulate the basic distinction -- one shared copy vs each app having its own -- and give a rough intuition that React-like libraries need singletons while utility libraries don't.
Should name the module federation shared/singleton config concretely and explain why context/hooks break with duplicate instances.
Should discuss the coordination cost singleton sharing imposes on team independence and describe concretely how version mismatches are handled (fallback vs strict failure).
Should reason about this as an organizational/architectural trade-off: which libraries justify the coupling cost of a shared singleton contract across teams, and how to draw that boundary as the platform scales.
## Why duplicate copies appear When a page is assembled from several independently deployed microfrontends -- say a checkout team's app, a navigation team's app, and a recommendations team's app -- each one is typically built with its own copy of shared libraries like React, a state manager, or a design-system component kit baked into its bundle. If every microfrontend simply ships its own copy, the browser ends up downloading and running multiple copies of the same library side by side on one page. - For **stateless libraries** this is only wasteful. - For libraries that carry **global or module-level state**, it is actively broken, because JavaScript module resolution is per-bundle -- two independently bundled copies of React are two entirely different objects in memory, even though the source code is identical. ## The mechanism that fixes it The mechanism that fixes this is 'singleton' sharing, most commonly configured through a module-federation-style runtime (webpack/Rspack Module Federation's `shared` config, or an equivalent native-federation setup). Each microfrontend declares which packages it wants to share, at what version range, and with `singleton: true`. At runtime, the federation runtime resolves all the declared shared dependencies from every loaded microfrontend into one shared scope. 1. The **first** microfrontend to request a singleton package gets to provide it (or the host provides it ahead of time). 2. Every **subsequent** microfrontend that also declares that package as a singleton is handed the exact same module instance instead of loading its own bundled copy, even though webpack still emitted separate chunks containing that code as a fallback. **Scoped** (non-singleton) sharing is the default absence of this mechanism: nothing is deduplicated, and each remote's own bundled copy is used, isolated in its own module scope. ## Why the distinction exists The reason this distinction exists is that a browser page is a single JavaScript runtime shared by every microfrontend on it, but the libraries were built as if each microfrontend were the only app on the page. Libraries built around a single global instance assume there is exactly one of them holding state that every component reads and writes: - React's reconciler and its Context API - a Redux/Zustand store - a routing library's history object - a design system's theme provider If two microfrontends each load their own React, a component tree rendered by microfrontend A and a `useContext` call made by microfrontend B's component will not see each other's state, because they belong to different React instances with different internal fiber trees. Singleton sharing forces there to be exactly one such instance, restoring the assumption the library was written under. ## The trade-off The trade-off cuts directly against microfrontend architecture's core promise of independent deployability. | Mode | What it buys | What it costs | |---|---|---| | **Scoped sharing** | keeps microfrontends fully decoupled: team A can upgrade to a new React version the day it ships without asking team B to do anything | at the cost of extra bytes downloaded (a duplicated React runtime is roughly 40-45KB gzipped, multiplied by however many microfrontends bundle it) and, for stateful libraries, outright breakage rather than just waste | | **Singleton sharing** | eliminates the duplication and the cross-instance breakage | but it re-introduces a coordination dependency between teams: everyone sharing a singleton is implicitly agreeing to stay on a mutually compatible version, which is exactly the kind of lockstep-release coupling microfrontends are usually adopted to avoid | Teams typically resolve this by singleton-sharing only the handful of libraries that truly require a single instance (framework, router, global state, design-system context providers) and leaving everything else -- utility libraries, one-off widgets, anything without shared mutable state -- scoped. ## How it shows up in production - **Singleton misconfiguration** shows up as a specific, recognizable class of bug: 'Invalid hook call,' 'cannot read properties of null (useContext),' or a UI theme/provider silently not applying to a nested microfrontend, all because a second, un-deduplicated copy of the library snuck in -- often because one microfrontend declared a version range the federation runtime judged incompatible with what was already loaded, so it silently fell back to bundling its own copy rather than failing loudly. - Conversely, **over-eager singleton sharing** shows up as deploy-time breakage: microfrontend A ships a major version bump of a shared library, and because it's a strict singleton with no fallback, every other microfrontend that hard-depends on the old API breaks in production the moment A deploys, even though A's own code works fine. ## A worked example A concrete, widely referenced example is Webpack Module Federation's own documented pattern: a host app and several remotes all declare `react` and `react-dom` as `{ singleton: true, requiredVersion: '^18.0.0' }` in their `shared` config, while a utility like `dayjs` is left unshared because each remote bundling its own few kilobytes of date formatting causes no correctness issue, only a negligible size cost.
- Which kinds of libraries almost always need to be singletons, and which are usually safe to leave scoped?Libraries with module-level or global mutable state that consumers implicitly share -- the UI framework itself (React/Vue), routing libraries, global state stores, and any context/theme provider -- need to be singletons because two instances can't see each other's state. Pure, stateless utility libraries like lodash, date formatters, or validation helpers are safe to leave scoped since duplication only costs bytes, not correctness.
- If two microfrontends declare incompatible major versions of a singleton library, what typically happens instead of a hard error?Most federation runtimes don't hard-fail by default; they emit a console warning and let the version that was loaded first (or the host's version) win, silently falling back to letting the other microfrontend bundle and use its own separate copy. This 'silent fallback' is exactly what re-introduces the multiple-instance bug the singleton was meant to prevent, which is why many teams also set `strictVersion: true` to fail loudly instead.
- How does singleton sharing affect a microfrontend's ability to deploy independently?It reintroduces coupling: since every consumer must accept the same instance, someone has to decide whose version 'wins' when ranges diverge, effectively requiring the sharing teams to coordinate compatible version bumps. This is a direct tension with the independent-deployability goal that motivates microfrontends in the first place.
Singleton sharing is like a shared office printer everyone queues for -- one physical device, consistent state, but if someone changes its settings it affects everyone. Scoped sharing is like everyone buying their own personal printer -- no coordination needed, but you pay for N printers and they can't hand paper to each other mid-job.
saying these in an interview costs you the question
- Says microfrontends always share every dependency to save bytes
- Doesn't know why two React copies break context/hooks
- Thinks scoped sharing has no downsides at all
- Can't explain what happens on a version mismatch
- Assumes singleton sharing requires all teams to release in lockstep with zero fallback options