Partition by business domain
Route or large domain boundaries reduce cross-team UI chatter; individual widgets are usually too fine-grained.
FRONTEND SYSTEM DESIGN
Design a system where independently deployed teams compose one cohesive product experience.

THE INTERVIEW SEQUENCE
Clarify scope
Set requirements
Draw data flow
Defend trade-offs
Start with the smallest coherent design. After the main path works, introduce failure, scale, accessibility, observability, and release strategy one constraint at a time.
Five to ten domain teams need genuinely independent release ownership inside one customer journey.
A shell owns identity, navigation, routing, observability, error isolation, and the shared design system.
Initial loading and runtime integration must fit an explicit performance budget; duplicate framework copies are unacceptable.
Every remote can be unavailable or incompatible without turning the entire application into a blank screen.
Route or large domain boundaries reduce cross-team UI chatter; individual widgets are usually too fine-grained.
Versioned navigation, auth capability, telemetry, locale, and design tokens avoid direct access to another team's store or DOM.
Build-time packages maximize predictability; runtime federation enables independent release but adds compatibility, security, and availability work.
Manifest validation, timeouts, error boundaries, canaries, and previous-version fallback make independent deployment survivable.
The shell validates identity, compatible contract range, entry URL, integrity, and route ownership before loading code.
type RemoteManifest = { name: string; version: string; shellContract: string; entry: string; integrity: string; routes: string[]; requiredCapabilities: string[] };type ShellContext = { navigate(to: string): void; locale: string; emit(event: TelemetryEvent): void };Impact: A route or domain disappears.
Mitigation: Use bounded timeouts, a domain error boundary, retry and previous-known-good or server fallback.
Impact: Runtime hooks, context, or rendering fails.
Mitigation: Declare tested version ranges, singleton only where required, validate manifests, and reject incompatible releases before traffic.
Impact: One domain silently alters another.
Mitigation: Use design tokens and scoped layers, forbid global selectors, and communicate only through documented contracts.
Impact: An independent deployment breaks production.
Mitigation: Version contracts, run provider-consumer tests, support overlapping versions, and canary the compatibility matrix.
Drive the discussion yourself first. This is one strong answer delivered the way you would say it in the room — a model, not the only correct design.
Start by pushing back: micro-frontends are a solution to an organizational problem, not a technical one, and they cost you a lot. Only reach for them when independent deployment by separate teams is a hard requirement. Once you have established that, the answer is about the shell, the integration contract, and not shipping React three times.
Step 1 · Question the premise
Say: 'Before designing this, is independent deployment actually required? If one team can own a modular monolith, that is simpler, faster, and lighter. Micro-frontends make sense when several teams must ship on their own cadences without coordinating a release.' Assume the interviewer confirms that is the case: 'multiple teams, each must deploy independently, and they cannot all be blocked by one release train.'
Step 2 · Requirements
User-facing: the product feels like one coherent app — consistent look, one navigation, one login. Hidden: each team builds, versions, and deploys its piece separately; teams communicate through a defined contract, not by reaching into each other's code; and the composed page still has one accessible structure.
Step 3 · The shell owns the frame
'A shell application owns the top-level layout, the navigation, authentication, and deciding which micro-frontend mounts into which region for the current route. Each micro-frontend exposes a defined integration contract to the shell — a mount and unmount lifecycle — and is otherwise a black box to the shell.'
Step 4 · Boundaries follow business domains
'The right places to split are stable business domains or route sections with clear team ownership — checkout, search, account — not small visual components that would need constant cross-team coordination. If two micro-frontends have to change together every time, the boundary is in the wrong place.'
Step 5 · Shared design layer and communication contract
'All micro-frontends consume a shared design-token and component layer, so visual and accessibility consistency does not depend on every team reimplementing it. Cross-micro-frontend communication goes through an explicit, minimal, versioned contract — a small event bus or a shared state slice for genuinely cross-cutting events like a profile update — never by one team importing another team's internal store.'
Step 6 · Not shipping React three times
'A shared framework like React is treated as a versioned, compatibility-tested shared dependency, enforced by dependency rules, and I would inspect the real production bundle to confirm it is not duplicated. Getting this wrong is the classic micro-frontend performance failure.'
Step 7 · When a remote fails
'Each remote loads behind a timeout and an error boundary. If checkout's micro-frontend fails to load, that region shows a retry or a fallback, but the shell navigation and the rest of the page keep working. Auth tokens flow from the shell down through the defined interface, scoped, not handed out raw.'
How to close
'So, having confirmed independent team deployment is a hard requirement: a shell owns layout, nav, auth, and mounting. Micro-frontends are split along business domains with clear ownership and expose a mount/unmount contract. A shared token and component layer keeps consistency; a minimal versioned event contract handles cross-cutting communication. The framework is a single compatibility-tested shared dependency, verified in the real bundle. A failed remote is contained by a boundary. What I would monitor: remote load and execution failure rates, the version pairs in production, route latency, and duplicate-bytes regressions correlated with releases.'
When one small team can release a modular monolith; independent deployment must justify runtime, governance, testing, and performance complexity.
DESIGN CHECKLIST
Use this checklist to rehearse the case without reading the worked answer above.
Do not design yet. Use these questions to make hidden assumptions explicit and prevent solving the wrong problem.
What's driving the split — independent team deployment cadence, or a specific technical constraint (legacy migration, differing frameworks)?
Is this split by page/route, or multiple micro-frontends composed on the same page?
Do all micro-frontends share the same framework/version, or must the architecture support heterogeneous stacks?
What's the shared-state and cross-micro-frontend-navigation requirement?
These are the user-visible capabilities the design must support. They define the first version's scope.
Multiple independently built and deployed frontend applications compose into one cohesive product for the end user.
Shared cross-cutting concerns (auth, navigation shell, design system) are consistent across all micro-frontends.
Navigation between micro-frontends feels seamless, not like separate page reloads between disconnected apps (unless that's an accepted tradeoff).
One team's deploy doesn't require coordinated deploys from other teams for unrelated changes.
These qualities shape the architecture even though users do not click them directly: latency, resilience, accessibility, consistency, and scale.
Failure isolation: one micro-frontend crashing shouldn't take down the entire shell or unrelated micro-frontends.
Aggregate bundle size and duplicate dependencies across micro-frontends must stay bounded, not grow linearly with team count.
Consistent look, feel, and accessibility behavior despite independent teams and possibly independent tech stacks.
Now assign responsibilities to clear boundaries. Each part should have one reason to change and an explicit contract with the next part.
A shell application owning the top-level layout, navigation, authentication, and orchestration of which micro-frontend mounts where.
Each micro-frontend independently built, versioned, and deployed, exposing a defined integration contract (e.g. a mount/unmount lifecycle) to the shell.
A shared design-token/component layer consumed by all micro-frontends so visual and accessibility consistency doesn't depend on every team reimplementing it correctly.
An explicit, minimal contract for cross-micro-frontend communication (e.g. a small event bus or shared, versioned state slice) rather than ad hoc direct coupling between teams' internals.
Trace one important user action from input to rendered result. This exposes ownership, race conditions, retries, and stale-data paths.
The shell resolves the current route/context and determines which micro-frontend(s) to load and mount into designated regions of the page.
A micro-frontend fetches its own data and manages its own internal state, only surfacing cross-cutting events (e.g. 'user updated profile') through the shared contract when other micro-frontends genuinely need to react.
Shared concerns like auth tokens flow from the shell down to each micro-frontend through a defined, versioned interface rather than each micro-frontend independently reimplementing auth.
There is no perfect architecture. State what each choice optimizes, what it costs, and the signal that would make you revisit it.
Micro-frontends buy independent deployability at the direct cost of duplicated dependencies and a harder-to-reason-about end-to-end experience; this is worth it only when the organizational scaling problem it solves (many teams, one product) is real.
A same-framework-everywhere constraint keeps shared tooling and bundle deduplication simple but sacrifices the flexibility to let a team choose a different stack, which is often the entire motivating reason for micro-frontends in the first place.
A rich, chatty cross-micro-frontend communication layer reintroduces the tight coupling micro-frontends were meant to avoid; the contract should stay deliberately minimal.
Scale the measured bottleneck rather than every box. Frontend scaling includes payload size, main-thread time, rendering work, cacheability, and release safety.
Share common heavy dependencies (framework runtime, design system) via a shared, cached module rather than bundling a full copy into every micro-frontend.
Version the integration contract explicitly (like a public API) so the shell and micro-frontends can evolve independently without breaking each other silently.
Invest in shell-level error boundaries so a failure in one micro-frontend degrades gracefully instead of white-screening the whole page.
Interviewers often probe these failure modes. Name them before being prompted and explain the guardrail you would add.
Adopting micro-frontends for a single small team with no real independent-deployment need, paying the architectural complexity cost for no corresponding benefit.
Letting micro-frontends reach into each other's internals directly instead of communicating through the defined shell contract, recreating tight coupling under a new name.
Not deduplicating shared framework/runtime dependencies, so total page weight balloons as more micro-frontends are added.
Your closing summary
Restate the critical user flow, the most important quality target, the architecture boundary that protects it, and the trade-off you accepted. Then name the first metric you would watch after launch.
Back to all cases →