Shared Singleton Skew
also called Duplicate Framework Instance, Singleton Version Mismatch
The failure class created when independently built front-end bundles expect different versions of a library that must exist once per page, producing either incompatible reuse or two isolated copies that share no state.
A shell loads three remote modules at runtime. Two were built against one major version of the UI framework and one against the next. Every remote's own pipeline is green, local development works because only one remote runs, and production produces errors about invalid hook calls and missing context.
Two outcomes are possible and both are bad. The incompatible shared copy is reused, and internals break because component state and context live in module-level variables inside that copy. Or a second copy loads, in which case each works internally and they share nothing: two component trees, two context providers, two routers contending for the URL, and a component from one unable to read a context from the other. The second is worse, because it half works and surfaces as inexplicable behaviour rather than an error.
Why it matters
The invalid combination is a deployment composition, not a build, so no team's CI can detect it. It exists only once the shell loads a particular set of remote versions together, which means production is the first place it is assembled — and the failure is time-dependent, because load order and cache state decide which copy wins, so it appears intermittently and differently per user.
It also propagates further than the framework. Routing, theming, internationalisation, authentication and analytics are all typically singletons, so a duplicated framework quietly duplicates everything built on it. The observable symptom is often three layers away from the cause.
Implementation patterns
- Declare every singleton as a strict shared dependency with a required version range, so a mismatch fails loudly at load time with a legible message instead of a hook error deep in a stack. A loud failure at composition is worth more than a subtle one at runtime.
- Own the version policy centrally. Publish a supported range and treat a framework major version as a coordinated upgrade with a stated window, typically a quarter with a deadline. Runtime composition means a shared runtime, which means coordinated upgrades.
- Test the composition in CI. A job that loads the shell against the current production version of every remote and runs a smoke journey. This is the gate that makes runtime composition safe, and its absence is the actual defect in most estates.
- Version the contract as well as the code. A remote declares an interface version; the shell checks it before mounting and renders a fallback rather than crashing the page.
- Keep the singleton list short and explicit — framework, router, design system, analytics. Anything that does not need sharing should be bundled per remote even at a cost in bytes, because each shared library is a coordination obligation.
- Pin by digest in the shell's manifest and roll forward deliberately, so the composition is a deployed artefact rather than whatever each remote happened to publish.
Industry example
The pattern is inherent to runtime composition and is the most reported operational problem with module federation since it appeared in Webpack 5 in 2020. It is also not new: the same class produced diamond dependency problems in every plugin architecture that shares a runtime, and the same two outcomes — incompatible reuse or duplicated instances — appear in native plugin systems and in JVM class-loader hierarchies.
The architectures that genuinely deliver independent release cadence are the ones that isolate at a boundary the platform enforces: separate pages with real navigation, or iframes. Both are unfashionable, and both remove this failure class entirely, which is the honest comparison to make before choosing federation.
Failure scenarios
- Invalid hook calls or missing context immediately after a remote is deployed, with no change to the shell.
- Two routers fighting over the URL, producing navigation that flickers or reverts.
- Duplicate theme or locale providers, so part of the page renders in the wrong language or palette.
- Authentication state held twice, so one region of the page believes the user is signed out.
- Intermittent, user-specific failures driven by cache state and load order, which resist reproduction.
- A remote upgraded for a security fix that cannot be deployed, because the shell's shared range forbids it and the coordination has not been scheduled.
Trade-offs
| Choose | Gains | Pays |
|---|---|---|
| Strict shared singletons | Mismatch fails fast and legibly | A remote cannot upgrade alone; coordination required |
| Permissive sharing | Remotes upgrade independently | Duplicate instances and state that silently does not merge |
| Build-time composition in a monorepo | This class disappears | A shared build and coupled deployments |
| Iframes or separate pages | Genuine independence | Navigation cost, duplicated shell weight, harder integration |
When not to use it
The concept applies wherever runtime composition exists; the question is whether to accept the architecture at all. If four teams share one application and deploy weekly, the coordination cost of runtime composition exceeds the coupling it removes, and a monorepo with build-time composition and a merge queue gives independent development with none of this failure class. Federation earns its place in a large estate where a full rebuild is genuinely too slow or where teams ship on materially different cadences — and even then it needs the composition pipeline before the first remote goes live, because without it the estate has bought a failure mode and none of the safety.
Interview question
Q: A shell loads five remotes. One team needs to upgrade the shared framework for a security fix. Walk me through what you check, what you change, and what you would have built earlier to make this routine.
What a strong answer covers: that the shared runtime makes this a coordinated upgrade rather than a local one; the two runtime outcomes and why duplication is worse than a hard failure; strict sharing with version ranges so incompatibility is loud; the composition CI job as the mechanism that makes the upgrade testable; a compatibility version checked before mounting with a fallback; and the architectural reflection that genuine independence requires browser-enforced isolation.
Quick check
Quiz: Why is a duplicated framework copy worse than an incompatible shared one? Because both copies work internally while sharing no state, so context, routing and authentication silently disagree instead of failing visibly.
Flashcard: Which CI job prevents shared singleton skew? — One that loads the shell against the current production version of every remote and runs a smoke journey, because the invalid combination is a deployment composition that no single remote's pipeline can see.