cswork / libraryOpen practice workspace

Articles

React Error Boundary Interview Questions: Failure Scope, Retry, and Recovery

Prepare for React error boundary interviews with a render-failure matrix, subtree placement, retry design, preserved form state, and recovery tests.

A results panel throws while rendering a search page. Should the entire app disappear, should the panel show a retry button, and what happens to the user's unsent form draft? Those are the useful React error boundary interview questions. A strong answer places the boundary around the part that can fail independently, identifies which errors React can route to it, and makes recovery retry the failed work without discarding unaffected state.

The behavior below is grounded in the current React Error Boundary reference and React's render-error lint guidance. The page, failure matrix, and tests are original interview exercises, not a report that a particular employer asks these questions.

What does an error boundary actually catch?

An error boundary is a React component that replaces its descendant subtree with fallback UI when React encounters a qualifying error while rendering that subtree. The classic implementation is a class component with static getDerivedStateFromError to select the fallback and optional componentDidCatch for reporting. React's official reference says there is currently no direct function-component equivalent to those class lifecycle methods; applications can reuse a class boundary or a library component.

The word descendant matters. A boundary cannot catch an error thrown in itself. A boundary around a results panel can keep a sibling form and navigation mounted; a boundary wrapped around the whole route may replace all three. That is a product decision as much as a coding decision. The candidate should identify the smallest useful recovery region, then check whether its siblings are genuinely independent.

Do not claim that any exception anywhere in the JavaScript application is caught. React's documented caveats exclude event handlers, ordinary asynchronous callbacks such as timers, server-side rendering, and errors in the boundary itself. Current React documents a special case for errors thrown inside the function passed to startTransition returned by useTransition; avoid an overbroad “all async work” rule. A rejected promise used during rendering can reach the nearest boundary through React's use behavior. State the exact path rather than guessing from the word “async.”

Where should the boundary sit?

Use a page with a search form, a results panel, and navigation. The search form has a typed but unsent query. The results panel renders a newly fetched record and throws because a field has the wrong shape. Put the boundary around the results panel if the form and navigation can still function and the fallback can honestly explain that results are unavailable. Put a larger boundary around the route if every control depends on the failed data and leaving them visible would mislead the user.

A very fine boundary around every tiny element is also expensive to reason about. It can yield a page full of partial fallback fragments with no coherent recovery. React's reference frames granularity around where it makes sense to display an error message. In an interview, explain the user-visible failure scope first, then the component placement. “Wrap the whole app for safety” leaves the important design question unanswered.

Failure locationBoundary around results panelWhat remains availableNext action
Result card throws while renderingCaught by its nearest ancestor boundaryForm draft and navigation stay mountedShow panel fallback; inspect data contract
Results boundary fallback itself throwsThis boundary cannot catch itselfDepends on a higher boundaryFix fallback or let a parent boundary handle it
Search button handler throwsNot caught by a render boundaryCurrent UI may remain visibleHandle or report in the handler's own flow
Timer callback throwsNormally outside boundary handlingDepends on global error behaviorHandle rejection/callback failure explicitly
Server render failsNot caught by this client boundary mechanismFramework/server behavior variesUse server/framework error handling

This matrix is an original diagnostic asset. It summarizes the cited React contract; it is not an exhaustive map of a framework's router or streaming server behavior. If the interview includes a framework, ask which version and which server/client boundary it uses.

How would you implement the fallback?

A minimal class boundary should keep only the state needed to decide between children and fallback. For example, getDerivedStateFromError can set failed: true; componentDidCatch can send the error and component stack to reporting. The fallback should be a real UI state: a concise message, an accessible retry control if retry is meaningful, and an alternative route when the panel is essential. Never expose a raw stack trace to users.

class ResultsBoundary extends React.Component {
  state = { failed: false };

  static getDerivedStateFromError() {
    return { failed: true };
  }

  componentDidCatch(error, info) {
    reportRenderFailure(error, info.componentStack);
  }

  render() {
    if (this.state.failed) return this.props.fallback;
    return this.props.children;
  }
}

This is deliberately small: it does not define retry yet. A button that only calls setState({ failed: false }) will render the same broken child again if the input has not changed. An interviewer should ask what will be different after retry: a refetch, a corrected payload, a different route, or a reset of the failing child. An empty “Try again” button is theater, not recovery.

Logging must be observably separate from rendering. componentDidCatch is the documented reporting location; it should avoid crashing the fallback if telemetry fails. Record enough context to group failures without collecting sensitive form content by default. Keep a correlation ID or request ID when one exists, and make duplicate reporting behavior explicit during development and production checks.

What should retry reset, and what should it preserve?

There are two independent operations: make the failed input healthy, and clear the boundary's failed state. Suppose the results component reads record.details.name but the server briefly returned no details. A refetch that returns a valid record addresses the underlying data problem. Resetting the boundary or remounting a keyed boundary gives React a chance to render the child again. If the bad record remains, the fallback should reappear; that is a correct signal.

A keyed remount is a simple interview illustration: render <ResultsBoundary key={attempt} ...> and increment attempt only after arranging a new fetch or retry condition. React treats a changed key as new identity, so state inside that boundary subtree resets. The unsent query should live outside the keyed subtree if it must survive. A library such as react-error-boundary may provide a reset API and resetKeys; if using it, distinguish library behavior from React's built-in class methods. React's use retry example uses a library boundary with resetKeys tied to a new promise, not a magic recovery of the rejected promise.

Consider three versions of the same page. In version A, the form, results, and navigation are all children of a keyed boundary: retry wipes the unsent form. In version B, only results sit inside it: retry preserves the form. In version C, the results component holds pagination and selection state that users expect to retain; a full remount loses them. Version C may need a more precise reset or state lifted outside the failed subtree. The correct answer follows the product state contract, not a blanket “always change the key.”

Why doesn't try/catch around JSX solve it?

A parent that returns <Results /> is creating a React element, not necessarily executing the child's render inside the parent's synchronous try block. React renders the child later in its own process. The official error-boundaries lint rule explicitly rejects a parent try/catch around JSX as a way to catch a child's render error. Use a boundary for that case.

Ordinary try/catch still belongs where it can catch an awaited operation in the same function. An event handler that calls an API can catch or handle its own rejected request and show an inline error state. That inline error may be preferable to throwing into a render boundary because the form itself is still healthy. A render boundary is a last containment line for rendering failures, not a replacement for validation, network error states, or typed result handling.

Be precise about Suspense as well. A pending promise consumed by React can show a Suspense fallback; a rejected promise can route to an error boundary. Loading and failure are different states. Wrapping a component in Suspense alone does not supply an error message for rejection. A robust answer shows both boundaries when that data-flow model is part of the exercise.

What would the interviewer test?

Start with the observable user contract: a healthy page shows query, navigation, and results. Inject a render failure in one result and assert that the panel fallback appears, the typed query stays intact, and navigation still works. Then repair the input, trigger retry, and assert that the panel returns. This is more informative than asserting only that an error message exists.

Next test the boundaries of the mechanism. Throw from an event handler and show that the render boundary is not the handler for it; verify the application's separate error path. Make the fallback itself fail in a test fixture and confirm a higher boundary takes over or the test fails loudly. Repeat with the boundary placed around the whole route to show the different state-loss outcome. If a data-loading library retries automatically, ensure the retry count and UI message agree rather than piling an independent button on top.

Finally, test accessibility: move focus into the panel, induce failure, and make sure focus does not remain on a removed DOM node. The fallback message should be perceivable, its retry button operable by keyboard, and recovery should put focus somewhere sensible. The exact announcement and focus movement depend on the UI, so test with the actual browser and assistive technology instead of treating an ARIA role as a guarantee.

FixtureExpected visible resultHidden regression it exposes
Child render throws once, then healthy data arrivesPanel fallback, then successful retryReset button without changed input
Form draft is typed before panel failureDraft remains after failure and retryBoundary wraps too much state
Fallback component throwsHigher boundary or explicit failureUnreliable recovery UI
Event handler rejectsInline or handler-owned error stateClaiming the boundary catches all errors
Retry refetch returns the same invalid shapeFallback returns with useful error reportingInfinite reset loop

A concise interview answer

“I would place an error boundary around the independently recoverable results subtree. React can show its fallback when a descendant fails while rendering, but this is not a general handler for event callbacks, ordinary timers, or server rendering. I would report with componentDidCatch, keep the unsent search form outside the reset subtree, and make retry obtain a new valid input before resetting the boundary. My test would force a broken record, verify the fallback and preserved form draft, then supply a healthy record and confirm recovery. I would also test a repeat failure so retry never loops silently.”

That answer earns its confidence by naming failure scope, a concrete cause of recovery, and a falsifiable test. If an interviewer asks about react-error-boundary, add that its resetKeys and fallback helpers are library APIs; React's built-in error boundary contract is the class lifecycle behavior described above.

Sources and further reading