skip to content

A dashboard re-requests its data every 30 seconds and every panel blanks back to its loading placeholder on each poll — why, and what fixes it?

level: seniorimportance: should knowfreq 54%

answer

  1. two facts, not one flag
  2. placeholder only when nothing is held
  3. refetching is its own branch
  4. do not clear the value on request start
  5. delay and minimum display thresholds

basics

~20 s

Each panel branches on “a request is in flight” instead of “there is nothing to show”. With a previous value held, keep rendering it and mark progress quietly; reserve the placeholder for the load that has nothing to replace.

solid answer

~50 s

The panels treat pendingness as one state, so a refresh looks exactly like a first load and the readable content is thrown away every 30 seconds. Two independent facts decide the branch: whether a value is currently held, and whether a request is in flight. Only the combination "in flight and nothing held" earns the placeholder. "In flight with a value held" is a distinct **refetching** state that renders the existing content plus a subdued progress hint, at identical layout, so nothing moves. Two refinements help beyond that: delay the placeholder by a short threshold so quick responses never flash one, and if a placeholder does appear, hold it briefly so it does not disappear in a blink. How the previous value is retained between requests is the caching layer's concern; what the component renders while the new one arrives is this decision.

go deeper

for a junior

Remember that a refresh is not a first load. If something is already on screen, keep it there while the new request runs instead of switching back to the placeholder.

for a middle

Explain the two independent facts — value held, request in flight — and the four combinations they produce. Name the placeholder-delay and minimum-display thresholds and what each removes.

for a senior

Diagnose it from the symptom, point at the value being cleared on request start, and design the refetching branch: layout held, progress subtle, view state preserved, failed refresh non-blocking.

for a principal

Set the default across surfaces: what a background refresh may and may not disturb, which thresholds apply, and how polling is suspended when nobody is looking. Inconsistency here is what makes an app feel unstable.

## Why the screen blanks The panel's render code almost certainly looks like "if a request is in flight, render the placeholder". That is correct exactly once — on the first load, when there is nothing else to render. Every subsequent request also sets the same condition, so the panel discards content the user was reading and reserves the region for a value it already has. Pendingness is not one fact. There are two, and they are independent: | Value currently held | Request in flight | Render | |---|---|---| | no | yes | Placeholder, shaped like the coming content | | yes | yes | The held value, plus a quiet in-progress hint — **refetching** | | yes | no | The value | | no | no | Empty branch, or idle if nothing has been requested | Branching on the pair rather than on the flag alone is most of the fix: the placeholder is reserved for the state with nothing to preserve. There is one honest exception. If the new request is for a *different subject* — another record, another account, another date range — the value on screen no longer describes what the user asked for, and keeping it is a lie rather than a courtesy. Treat a subject change as a first load for that subject and let the placeholder appear; treat a refresh of the same subject as refetching. ## What the refetching branch should look like - **Keep the content and the layout.** No size change, no re-mount, no scroll jump. If the new data is a different size, that shift happens once when it lands, not twice. - **Signal progress subtly** — a thin progress line on the container, a small spinner in the panel header, a slight reduction in contrast. The user should be able to ignore it. - **Keep the content interactive**, unless acting on it would be misleading. If a row is about to disappear, disabling one control beats freezing the panel. - **Do not reset local view state.** Sort order, expanded rows, scroll position and text selection all belong to the user, not to the request. - **Say when it last succeeded** on surfaces where staleness matters; a timestamp is more honest than an animation. - **Distinguish the refetch that fails.** A failed background refresh with a good value on screen is not a full error branch: keep the value, show a non-blocking notice, and offer a retry. ## Timing: two thresholds, not one Even a correct branch can be annoying, because very short and very long requests want different treatment. 1. **Delay before showing a placeholder.** For a request that resolves in a few tens of milliseconds, a placeholder appears and vanishes as a flash, which reads as a glitch rather than as progress. Waiting a short interval before rendering the pending branch removes it entirely for fast responses. 2. **Minimum display once shown.** If the placeholder has appeared, replacing it a moment later produces the same flicker at the other end. Holding it for a brief minimum makes the transition legible. 3. **A long-wait escalation.** Past a few seconds, a placeholder that says nothing is worse than one that acknowledges the wait and offers a way out — cancel, or retry. These thresholds are per-surface judgment, not universal constants, and they interact: a delay that exceeds a typical response time means the pending branch is effectively dead code on that surface, which is often fine. ## Where a poll makes it worse A periodic refresh magnifies every one of these mistakes, because it happens without the user asking: - A blank-and-restore every 30 seconds makes a stable screen feel broken. - A poll that continues while the surface is hidden or the connection is down burns requests and produces failure noise nobody is looking at. - Two overlapping refreshes can land out of order; which response wins is a separate concern from what renders, and belongs with request ordering rather than here. - A user-triggered refresh deserves *more* feedback than a background one, because the user is waiting for evidence their click did something — typically an indicator on the control they pressed rather than on the content. ## Announcing it Content that changes underneath a screen-reader user without any announcement is disorienting, and so is one that announces every poll. A reasonable default is to announce user-initiated refreshes politely, announce failures, and stay silent for successful background refreshes that did not change anything meaningful. ## Who owns what The component owns the rendering decision above. Whether a previous value still exists to render — retained across requests, shared with other components, revalidated on some trigger — belongs to the caching layer under it. A component with no cache beneath it can still implement all of this by keeping the last successful value in its own state instead of clearing it when a new request starts. **The bug is almost always literally that line**: clearing the value at the start of a request. ## How this shows up in review - A request handler that sets the value to nothing before it starts. - One flag named for loading that is read by both the first-load branch and the refresh path. - A placeholder shown for a request served in milliseconds. - A refresh that resets sort order or scroll position.

  • Which single line of code is usually the actual bug?
    Clearing the held value at the start of a new request. Once the value is gone, every branch downstream correctly concludes there is nothing to show, so the placeholder is the honest output. Leave the previous value in place until a response replaces it, and the refetching branch becomes expressible.
  • Why delay the placeholder rather than show it immediately?
    Because a placeholder that appears and disappears within a fraction of a second reads as a flicker, not as progress, and it moves layout twice for no information gain. A short delay before rendering the pending branch means fast responses never produce one, while slow ones still get feedback.
  • How should a failed background refresh be rendered when good data is on screen?
    Keep the data and add a non-blocking notice with a retry — the user still has something useful, and replacing it with a full error branch destroys value to report a transient problem. Say when the shown data was last confirmed, so the user can judge whether to trust it.
  • Does a user-triggered refresh want the same treatment as a background poll?
    It wants more feedback, and in a different place. The user is waiting for evidence their action registered, so an indicator on the control they pressed is better than one on the content, and it is reasonable to announce the result. Background refreshes should be nearly invisible when they succeed.

A waiter checking whether your next course is ready should not clear the plate you are still eating from.

saying these in an interview costs you the question

  • Clears the held value when a new request starts
  • Reads one loading flag for both first load and refresh
  • Shows a placeholder for a response that arrives in milliseconds
  • Resets sort order or scroll position on every refresh
  • Replaces good data with a full error branch when a refresh fails
  • Thinks keeping content on screen requires a dedicated cache layer