Skip to content

FRONTEND SYSTEM DESIGN

Design a Micro-frontend architecture

Design a system where independently deployed teams compose one cohesive product experience.

Micro-frontend architecture architecture diagram

THE INTERVIEW SEQUENCE

How to drive this discussion

01

Clarify scope

02

Set requirements

03

Draw data flow

04

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.

01Concrete scale assumptions

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.

02Architecture decisions and rationale

Partition by business domain

Route or large domain boundaries reduce cross-team UI chatter; individual widgets are usually too fine-grained.

Keep the shell contract small

Versioned navigation, auth capability, telemetry, locale, and design tokens avoid direct access to another team's store or DOM.

Choose integration per constraint

Build-time packages maximize predictability; runtime federation enables independent release but adds compatibility, security, and availability work.

Isolate failure and rollout

Manifest validation, timeouts, error boundaries, canaries, and previous-version fallback make independent deployment survivable.

03Client contracts and data boundaries

Versioned remote manifest

The shell validates identity, compatible contract range, entry URL, integrity, and route ownership before loading code.

TypeScript
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 };

04Failure-mode analysis

Remote cannot load

Impact: A route or domain disappears.

Mitigation: Use bounded timeouts, a domain error boundary, retry and previous-known-good or server fallback.

Shared dependency mismatch

Impact: Runtime hooks, context, or rendering fails.

Mitigation: Declare tested version ranges, singleton only where required, validate manifests, and reject incompatible releases before traffic.

CSS or global event collision

Impact: One domain silently alters another.

Mitigation: Use design tokens and scoped layers, forbid global selectors, and communicate only through documented contracts.

Shell contract drifts

Impact: An independent deployment breaks production.

Mitigation: Version contracts, run provider-consumer tests, support overlapping versions, and canary the compatibility matrix.

05Observability and operations

  • Record shell and remote versions together with load time, route transitions, errors, fallbacks, and Web Vitals.
  • Maintain a live compatibility and adoption matrix across shell and remote versions.
  • Use cross-boundary trace context while preserving domain ownership of detailed telemetry.
  • Set per-remote JavaScript, CSS, latency, and error budgets and block a release that violates them.

06Security and accessibility boundaries

  • Allow-list remote origins and manifests, enforce CSP and trusted publishing, and verify integrity where the delivery model supports it.
  • Expose scoped capabilities rather than raw long-lived tokens or unrestricted shell internals.
  • The composed page still needs one coherent heading, landmark, focus, announcement, and keyboard model.
  • Route transitions restore predictable focus and titles; remote loading and failure states are announced accessibly.

ANSOne full answer, start to finish

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.

  1. 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.'

  2. 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.

  3. 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.'

  4. 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.'

  5. 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.'

  6. 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.'

  7. 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.'

QAInterview questions and model answers

When one small team can release a modular monolith; independent deployment must justify runtime, governance, testing, and performance complexity.

Primary references

DESIGN CHECKLIST

Build your answer from first principles

Use this checklist to rehearse the case without reading the worked answer above.

CLARIFYING QUESTIONS

Do not design yet. Use these questions to make hidden assumptions explicit and prevent solving the wrong problem.

  1. 01

    What's driving the split — independent team deployment cadence, or a specific technical constraint (legacy migration, differing frameworks)?

  2. 02

    Is this split by page/route, or multiple micro-frontends composed on the same page?

  3. 03

    Do all micro-frontends share the same framework/version, or must the architecture support heterogeneous stacks?

  4. 04

    What's the shared-state and cross-micro-frontend-navigation requirement?

FUNCTIONAL REQUIREMENTS

These are the user-visible capabilities the design must support. They define the first version's scope.

  1. 01

    Multiple independently built and deployed frontend applications compose into one cohesive product for the end user.

  2. 02

    Shared cross-cutting concerns (auth, navigation shell, design system) are consistent across all micro-frontends.

  3. 03

    Navigation between micro-frontends feels seamless, not like separate page reloads between disconnected apps (unless that's an accepted tradeoff).

  4. 04

    One team's deploy doesn't require coordinated deploys from other teams for unrelated changes.

NON-FUNCTIONAL REQUIREMENTS

These qualities shape the architecture even though users do not click them directly: latency, resilience, accessibility, consistency, and scale.

  1. 01

    Failure isolation: one micro-frontend crashing shouldn't take down the entire shell or unrelated micro-frontends.

  2. 02

    Aggregate bundle size and duplicate dependencies across micro-frontends must stay bounded, not grow linearly with team count.

  3. 03

    Consistent look, feel, and accessibility behavior despite independent teams and possibly independent tech stacks.

ARCHITECTURE

Now assign responsibilities to clear boundaries. Each part should have one reason to change and an explicit contract with the next part.

  1. 01

    A shell application owning the top-level layout, navigation, authentication, and orchestration of which micro-frontend mounts where.

  2. 02

    Each micro-frontend independently built, versioned, and deployed, exposing a defined integration contract (e.g. a mount/unmount lifecycle) to the shell.

  3. 03

    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.

  4. 04

    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.

DATA FLOW

Trace one important user action from input to rendered result. This exposes ownership, race conditions, retries, and stale-data paths.

  1. 01

    The shell resolves the current route/context and determines which micro-frontend(s) to load and mount into designated regions of the page.

  2. 02

    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.

  3. 03

    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.

TRADE-OFFS

There is no perfect architecture. State what each choice optimizes, what it costs, and the signal that would make you revisit it.

  1. 01

    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.

  2. 02

    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.

  3. 03

    A rich, chatty cross-micro-frontend communication layer reintroduces the tight coupling micro-frontends were meant to avoid; the contract should stay deliberately minimal.

SCALING

Scale the measured bottleneck rather than every box. Frontend scaling includes payload size, main-thread time, rendering work, cacheability, and release safety.

  1. 01

    Share common heavy dependencies (framework runtime, design system) via a shared, cached module rather than bundling a full copy into every micro-frontend.

  2. 02

    Version the integration contract explicitly (like a public API) so the shell and micro-frontends can evolve independently without breaking each other silently.

  3. 03

    Invest in shell-level error boundaries so a failure in one micro-frontend degrades gracefully instead of white-screening the whole page.

COMMON PITFALLS

Interviewers often probe these failure modes. Name them before being prompted and explain the guardrail you would add.

  1. 01

    Adopting micro-frontends for a single small team with no real independent-deployment need, paying the architectural complexity cost for no corresponding benefit.

  2. 02

    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.

  3. 03

    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 →