For a client-side cache of server data keyed by the request, what does a stale entry mean and what does a reader get?
answer
- usable, not trusted
- a confidence claim, not empty content
- serve from memory, check behind it
- a missing entry means a real loading state
- arrival time plus a staleness window
basics
~20 sStale means the entry is still usable but no longer trusted to match the server. A reader gets the cached value immediately as data, not as loading, while the cache refetches behind it and re-renders on arrival.
solid answer
~50 sA cache of server-owned data stores each response under a key describing what was asked, with its arrival time and a staleness window. Inside that window the entry is `fresh` and a read is answered from memory, with no request. Past it the entry is `stale`: the value is still there and still shown, it is simply no longer trusted to match the server. The usual read policy is stale-while-revalidate — serve the stored value now, start one refetch in the background, replace the entry and re-render every subscriber when the response arrives. So a stale read is a success state, not a loading state and not an error: the reader shows data plus a quiet updating indicator. A *missing* entry — or a first attempt that failed before any value arrived — is what genuinely has nothing to show, and that is the case a loading state is for.
go deeper
Recall the three entry states a reader can meet: fresh, stale and missing. A missing entry is what justifies a loading placeholder; a stale one is shown as data while the cache checks behind it.
Explain the mechanics: arrival time plus a staleness window decides fresh versus stale, the read is served from memory, one background refetch per key replaces the entry, and every subscriber re-renders from that single response.
Show the judgment: which screens may show a stale value, which must confirm against the server before acting, and how you keep revalidation from blanking content or multiplying requests on a busy screen.
Frame staleness as a product decision with a traffic budget behind it. Argue which classes of data get which window, what the organisation does when a cheap window causes a visible wrong number, and how you would detect that.
## Why server data needs a cache of its own A component tree holds two kinds of value that behave nothing alike. The first is **owned by the tree**: whether a panel is open, what the user has typed, which tab is selected. Nobody outside the tab can change it, so the copy in memory *is* the truth. The second is **owned by the server**: an order, a price, a list of teammates. There the tree holds a **copy that was correct at the instant the response arrived**, and it can be wrong a second later because somebody else changed the record. That difference is why server data is not kept the way ordinary state is kept. It is stored in a cache **keyed by what was asked** — the resource, the filters, the sort, the page, and the identity of the caller. The key matters as much as the value: two components that ask the same question land on the same entry instead of firing two requests and holding two copies that can disagree on the same screen. ## Fresh, stale, and the read policy Each entry records when its data arrived and is governed by a **staleness window**: how long the stored value is treated as good enough to serve without checking. - **fresh** — inside the window. The read is answered from memory; no request is made. - **stale** — past the window. The value is still *usable*, it is just no longer *trusted*. `stale` is therefore a claim about confidence, not about content. The entry is not empty, not deleted and not broken. What a read does with it is a policy choice: | read policy | what the reader sees first | requests per read | what it costs | |---|---|---|---| | refetch first, render nothing until it lands | a loading placeholder, every time | one, blocking | slowest screen; the content flickers away on every visit | | serve from memory only | the stored value, indefinitely | none | the user can act on a value that is hours old | | stale-while-revalidate | the stored value immediately | one, in the background | a short window where the previous value is on screen | Most component frameworks and the data layers built for them default to the third row, because it is the only one that is both instant and self-correcting. ## The read path, step by step 1. A component subscribes to the cache for key `K`. 2. The entry for `K` exists and is fresh: serve the stored value, make no request. 3. The entry exists and is stale: serve the stored value **and** start one refetch for `K`. A refetch already in flight for `K` is joined, not duplicated. 4. The entry is missing: there is nothing to serve, so this reader really is in a loading state. 5. The response lands: the entry is replaced, its arrival time reset, and every subscriber of `K` re-renders with the new value. Step 5 is the part that surprises people: the refetch does not belong to the component that triggered it. It updates the entry, and everything reading that entry updates with it. ## What the reader has to handle - **Two success shapes.** Data with nothing in flight, and data with a refetch in flight. The second should keep the content on screen and show a subdued indicator at most. - **Never blank on revalidate.** Replacing rendered content with a placeholder because the entry went stale throws away the whole benefit of the cache and causes layout shift. - **A failed refetch over good data.** A stale value plus a failed check is still more useful than an empty screen; surface the failure without deleting the value. - **Values that must not be stale.** Anything the user is about to act on irreversibly — a total to be charged, a permission, remaining stock — is confirmed by the server as part of the action, not trusted from a cached read. ## Where this goes wrong in practice The two common failures are opposite. One is a staleness window of zero everywhere, so every mount refetches and the app behaves as if there were no cache at all, only a memory cost. The other is an unbounded window with no refetch triggers, so a long-lived tab quietly shows yesterday's numbers. A third, subtler failure is an **unstable key** — a key rebuilt from a freshly created object or an unpinned timestamp on each render. Every read then looks like a brand-new question, so the cache reports a miss rather than a stale hit, and the screen flashes a loading placeholder that looks like a refetch bug but is really a keying bug.
- Why does a stale read still show data while a missing entry shows a loading placeholder?Because the distinction is whether there is anything to render. A stale entry holds a value that was true recently, so rendering it is strictly better than rendering nothing and it will be corrected when the refetch lands. A missing entry holds nothing, so the first request for that key genuinely has no content to show and the reader must render a placeholder.
- If a background refetch returns exactly the same bytes, what should change in the cache?The arrival time, which makes the entry fresh again and stops further refetches until the window passes. The value should be treated as unchanged so subscribers are not forced to re-render or reset scroll and selection. Caches that replace the stored object unconditionally can cause avoidable renders even when nothing about the data moved.
- Two components read the same key at the same moment while the entry is stale. How many requests should leave the app?One. The cache owns the request for that key, so the second reader joins the refetch already in flight rather than starting its own. Both are served the stored value immediately and both re-render from the single response, which is the main reason the cache sits above the components rather than inside each of them.
A stale entry is like the departures board you photographed five minutes ago: still the best information you have, worth acting on, and worth glancing up at the real board while you walk.
saying these in an interview costs you the question
- Thinks a stale entry has been deleted or is unusable
- Renders a loading placeholder whenever an entry is stale
- Believes cached data must be correct because it rendered
- Treats staleness as a bug instead of a chosen window
- Assumes the cache polls continuously to stay fresh
- Trusts a cached total when confirming an irreversible action