How do you decide how many error boundaries a React app needs and where they go, given that a single boundary at the root already prevents every blank page?
answer
- boundaries declare acceptable degradation
- root-only equals a nicer blank page
- what still works when this fails?
- never wrap the user's in-progress work
- a silent boundary hides an incident
basics
~20 sPlace boundaries by blast radius: a root boundary as the last resort, one per route so navigation recovers, and one around each region whose failure should not remove the page's primary task. Every boundary must report, or it becomes a way to hide bugs.
solid answer
~50 sA root-only boundary turns a blank page into an apology page, which is barely better — the user still lost everything. The real question is what should keep working when a given region fails, so I place boundaries at the seams where degradation is meaningful: the root as a last resort, one per route so navigating away clears a stuck fallback, and one around each independently valuable widget — a recommendations rail, a chart, an embedded third-party component. I deliberately keep boundaries *outside* nothing the user has invested in: a boundary wrapping a half-filled form throws that work away when a sibling widget crashes, so the form gets its own boundary or none. Every boundary reports through `componentDidCatch` or React 19's root `onCaughtError`, with a component stack, and a contained error still pages someone if the rate rises. Boundaries change who notices a failure, not whether it exists.
go deeper
Understand that boundaries can be nested and that the closest one above the failure decides how much of the screen disappears — one at the app root means everything disappears.
Be able to justify a placement: a boundary per route and per independently loading region, with fallbacks that keep the rest of the page usable, rather than a single wrapper at the top.
Weigh state loss against containment — never wrap in-progress user work — and insist that every boundary reports with enough context to attribute the failure to a feature.
Own the whole policy: the degradation contract per surface, the alerting thresholds for contained errors, the taxonomy separating modelled failure states from boundary-worthy defects, and the fallback copy standard that stops users reloading past your containment.
## The question behind the question "How many boundaries?" is really "what is this page's primary job, and what may fail without stopping it?" Boundaries are a declaration of acceptable degradation, and only a product-aware answer is possible. An interviewer asking this is checking whether you reason about user outcomes rather than reciting an API. ## Why root-only is not enough One boundary at the root does prevent the blank page — it replaces the entire app with a fallback. From the user's perspective that is barely distinguishable from the crash: the checkout they were completing is gone, along with the form state, scroll position and open dialogs. Root-only also destroys diagnostic resolution: every failure looks like "the app broke", with the component stack as the only clue about where. So the root boundary is the last resort, not the strategy. ## The layers that usually earn a boundary **Root.** Catches everything nothing else did. Its fallback must be dependency-free — no data access, no context it might not have — because a fallback that throws escalates to an unmounted tree. **Route or page.** A boundary per route means a broken page does not poison the app shell, navigation still works, and moving elsewhere clears the failure. Keying this boundary on the route makes recovery automatic. **Independently valuable regions.** A dashboard is the canonical case: five panels, each fed by a different service. A boundary per panel converts "the dashboard is down" into "one panel says it is unavailable". The test is whether the region's failure should remove the page's primary task. **Untrusted or volatile subtrees.** Third-party embeds, user-authored content renderers, experimental features behind flags, anything rendering data whose shape you do not control. These fail in ways your own code does not, and containing them is high value for one wrapper. ## Where a boundary is actively harmful A boundary destroys the state below it. Wrapping a region that contains the user's in-progress work means an unrelated crash inside that region takes their typing with it. Concretely: do not put a single boundary around "the whole editor" if a sidebar widget inside it can crash — give the widget its own boundary so the editor survives. Similarly, a boundary around something whose failure makes the rest of the page meaningless buys nothing. If the order summary cannot render, a "summary unavailable" box beside a live "Pay" button is worse than a page-level failure. Degrade at a level where the remaining UI is still coherent and safe to act on. ## Boundaries versus modelled failure states Most failures should never reach a boundary at all. An expected empty result, a validation rejection, a request timeout — those are states the component should render deliberately, with retry affordances and no state loss. Boundaries are for the unexpected: a bug, an invariant violation, data the component genuinely cannot render. A team-level heuristic worth stating: if you find yourself adding a boundary to make a *known* failure look acceptable, you have found a missing state in the component's design, not a missing boundary. ## Observability is the non-negotiable half A boundary makes failure invisible to the user, which means it makes failure invisible to *you* unless you wire reporting. Two levers in React 19: - `componentDidCatch(error, errorInfo)` per boundary, adding local context — which feature, which entity, which flow; - `createRoot(container, { onCaughtError, onUncaughtError })`, giving one app-wide reporting site with the component stack, including for boundaries you forgot to instrument. Then decide the operational policy: contained errors are not free. Track their rate per boundary, alert when one climbs, and treat a chronically failing panel as an incident even though nobody saw a blank page. The failure mode of a well-boundaried app is that it degrades quietly for weeks. ## Fallback quality as a design decision If every boundary renders the same generic "Something went wrong", users learn to reload the whole page whenever anything goes wrong — which throws away the containment you just built. Fallbacks should say what is unavailable, what still works, and what the user can do: retry, continue without it, or reload. That is a design and copy decision, not a React one, and it belongs in the same review as the placement. ## Interaction with Suspense boundaries Error and Suspense boundaries answer different questions — "this failed" versus "this is not ready yet" — and their placement usually converges, since a region that can load independently is normally a region that can fail independently. Pairing them per region gives a coherent story: a skeleton while loading, a scoped error message when it fails, and the rest of the page untouched in both cases. ## Summary answer Root as the last resort, route level so navigation recovers, region level wherever independent degradation is meaningful, and never around the user's in-progress work. Pair each with reporting and an alerting threshold, and write fallbacks that describe the loss rather than repeating one generic apology.
- When is adding a boundary the wrong fix?When the failure is expected. A timeout, a 404, a rejected save are states the component should render on purpose, with a retry and no state loss. Reaching for a boundary there hides a missing design state and destroys the subtree as a side effect. Boundaries are for the unmodelled failure.
- How do you keep boundaries from hiding a systemic problem?Report every catch with enough identity to attribute it — `componentDidCatch` for feature-level context plus React 19's root `onCaughtError` as the safety net — then treat contained-error *rate* per boundary as a monitored signal with alert thresholds. A boundary that fires constantly is an incident nobody has been paged about yet.
- Should error boundaries and Suspense boundaries sit at the same places?Usually, because a region that can load independently is normally a region that can fail independently. Pairing them gives one coherent story per region: skeleton while pending, scoped error message on failure, page intact either way. They still answer different questions, so a mismatch is fine when a region can fail but never suspends.
- How would you decide whether a fallback offers 'Retry' or 'Reload the page'?By whether you can reset the cause. If the region owns a cache entry or a parameter you can clear and remount, retry is honest. If the failure came from state further up that the fallback cannot touch, a retry button will just re-show the fallback, and offering a reload — or a navigation away — respects the user's time more.
saying these in an interview costs you the question
- One root boundary is enough for any app
- More boundaries are always better, wrap everything
- Wrapping the whole editor including in-progress forms
- Generic 'Something went wrong' fallback everywhere
- Contained errors need no alerting because users saw nothing