skip to content

How does rendering a pending state from each component's own flags differ from one ancestor fallback covering a subtree that declared itself not ready?

level: middleimportance: must knowfreq 62%

answer

  1. many small placeholders versus one
  2. who owns the fallback
  3. the slowest descendant sets the reveal
  4. partial render work is discarded
  5. granularity moves to boundary placement

basics

~20 s

Per-component flags give each component its own placeholder: fine granularity, many indicators, coordination by hand. A subtree that declares itself not ready hands one ancestor fallback the whole region: one coherent placeholder, but everything inside waits for the slowest part.

solid answer

~40 s

With per-component flags, every component owns its own pending branch, so fast parts of a screen appear immediately and slow parts show their own placeholder. The cost is a screen speckled with indicators, and any ordering between them is manual. With the boundary model, a component that cannot render yet signals that upward, its partial output is discarded, and the nearest ancestor boundary renders one fallback covering everything below it. That produces a single coherent placeholder and a layout reservation decided in one place, but the whole covered region is withheld until the slowest descendant is ready, so boundary placement becomes the design decision. The boundary model also needs a runtime that can pause and discard a subtree mid-render, which not every framework offers; where it does not exist, flags are the only option.

go deeper

for a junior

Know that there are two ways to show a pending state: the component renders its own placeholder, or an ancestor renders one for a whole region. Notice how many placeholders a screen shows you.

for a middle

Explain the mechanism of the second model — pause, discard partial work, render the ancestor fallback, retry — and the runtime capability it requires. Name the tradeoffs in granularity and layout reservation.

for a senior

Show where you would place boundaries on a real screen and why, including the region you would deliberately keep on local state because the user is reading it. Diagnose a fast page made slow by a high boundary.

for a principal

Own the convention: which layers get boundaries by default, what a fallback must reserve, and how mixed models are kept from nesting into each other. The failure mode of no convention is inconsistent reveal behaviour across teams.

## Two ways a tree can say "not yet" A component that depends on a request has to communicate pendingness to whatever will render a placeholder. There are two mechanisms, and they are not variations of one idea. **Local flags.** The component tracks the state of its own request and renders its own branch. Nothing outside it participates. This works in any component framework, because it is ordinary conditional rendering over ordinary state. **A not-ready subtree with an ancestor fallback.** The component, while rendering, signals that it cannot produce output yet. The runtime stops rendering that subtree, throws away the partial work it had already done for it, walks up to the nearest boundary that declares a fallback, and renders the fallback in place of the whole subtree. When the dependency resolves, the runtime retries the subtree and swaps the real content in. This requires a runtime that supports pausing mid-render and retrying; frameworks differ on whether they have it at all. ## What actually differs | Dimension | Local flags | Ancestor fallback over a not-ready subtree | |---|---|---| | Granularity | Per component, as fine as you write it | Per boundary, chosen by where you put them | | Placeholders on screen | One per pending component — often several at once | One for the whole covered region | | Who owns the placeholder | The component that is loading | The ancestor, which may not know what it is covering | | Layout reservation | Each component reserves its own space | One fallback must reserve space for everything under it | | Fast content near slow content | Fast parts render immediately | Everything covered waits for the slowest descendant | | Coordination between regions | Manual, and easy to get wrong | Implicit: one region, one reveal | | Partial render work | Kept; the component rendered its own branch | Discarded for the paused subtree and redone on retry | | Runtime requirement | None beyond conditional rendering | Pause, discard and retry a subtree mid-render | ## The slow-widget question The comparison that interviewers actually reach for: a page is fast except for one widget in the middle of it. - With **local flags**, the page renders, the widget shows its own placeholder, and the user reads everything else. The screen looks busy, and if the widget later grows, the surrounding content moves. - With **one high boundary**, nothing appears until the widget is ready, which converts a fast page into a slow one. - With a **boundary around the widget alone**, the page renders and only the widget's region shows a fallback — the same user-visible result as flags, with the placeholder owned in one place. That is the real lesson: the boundary model does not remove the granularity decision, it relocates it from "which components render a branch" to "where the boundaries sit". Both extremes are bad. One boundary around a whole screen makes the slowest dependency the screen's latency. A boundary around every component reproduces the speckled look of flags, with extra machinery, and makes the reveal order hard to reason about. ## Layout is the sharper cost A per-component placeholder can be shaped like the one thing it replaces, so the swap barely moves the page. One fallback covering a large region has a harder job: it must approximate the footprint of everything beneath it, and it cannot know what that will be. Two common outcomes are a fallback much shorter than the content, so the page grows when the content lands, and a fallback padded to a fixed tall block, so the page shrinks. Neither is fatal, but this is why "wrap the page in one boundary" feels cheap and looks bad. ## What each model gives you afterwards - **Retry.** With flags, the failed component still knows its request parameters, so the retry trigger it renders re-runs exactly one request. With a boundary-style failure, the handler that catches it is an ancestor and typically has no idea what to re-run, so its affordance is coarser. - **Already-visible content.** With flags, you choose to keep the old value while a new request runs. With a boundary, content that was already revealed can be replaced by the fallback again if the subtree becomes not-ready a second time — usually undesirable, and the reason frameworks with this model also provide ways to keep the previous content while the new dependency resolves. - **Testing and reasoning.** A flag is a value you can inspect; a paused subtree is runtime behaviour you observe only through what renders. ## Mixing them Real screens usually mix: boundaries at route and section level to give a coherent first paint, and local flags inside interactive components for refreshes, where replacing content the user is reading would be a regression. The choice is per region, not per application. ## How this shows up in review - A boundary placed so high that one slow dependency delays an otherwise fast screen. - A fallback whose height bears no relation to the content it stands in for. - Both mechanisms active in the same region, producing a placeholder inside a fallback. - A pending branch written per component in a runtime that offers boundaries, with no reason given — or the reverse, boundaries assumed in a runtime that cannot pause a subtree at all.

  • What must a runtime be able to do before the boundary model is available at all?
    Pause a subtree part-way through rendering, discard the partial output it had produced, render an ancestor's fallback in its place, and retry the subtree when the dependency resolves. A runtime without mid-render pausing can only express pendingness as state a component reads and branches on.
  • Where would you rather not use one ancestor fallback?
    Around a region the user is already reading and interacting with. A refresh inside it would swap readable content for a placeholder and move the layout. Local state there lets you keep the previous value and mark progress quietly, which is almost always the better experience for a repeat load.
  • Does the boundary model remove the need to think about granularity?
    No — it moves the decision. Instead of choosing which components render their own branch, you choose where the boundaries sit, and the covered region waits for its slowest descendant. One boundary around everything and one around every component are both failure modes.
  • Why does the partial work of a paused subtree matter?
    Because it is thrown away and redone on retry. Anything the paused components did during that render that was not pure — writing to a store, starting a timer, mutating shared state — can run more than once, so side effects belong outside the render path in this model.

saying these in an interview costs you the question

  • Thinks every framework can pause a subtree mid-render
  • Wraps a whole screen in one fallback and calls it fine-grained
  • Assumes a fallback needs no layout reservation
  • Believes a paused subtree keeps the work it already did
  • Treats boundaries and local flags as interchangeable per region
  • Expects an ancestor handler to know which request to retry