skip to content

In a cache of server data that components subscribe to by key, what stages does an entry pass through before it leaves memory?

level: middleimportance: must knowfreq 58%

answer

  1. an entry has a life, not a value
  2. two clocks, different questions
  3. trust versus memory
  4. unused means no reader, not invalid
  5. collection starts when subscribers hit zero

basics

~20 s

An entry is created on first request, stays fresh inside its staleness window, goes stale past it, becomes unused when its last subscriber unmounts, and is collected after the retention window. Two independent timers govern it.

solid answer

~50 s

A cache entry has a life, not just a value. It is created when some component first asks for that key and the response lands. While inside its **staleness window** it is fresh and reads never hit the network. Past the window it is **stale**: still served, but a read now also triggers a refetch. When the last component reading the key unmounts, the entry becomes **unused** — nobody is subscribed, so nothing refetches it, but it is deliberately kept so that navigating back is instant. If no new subscriber appears within the **retention window**, it is **collected** and its memory released; the next read for that key is a cold miss with a loading state. The two windows answer different questions — how long a value is trusted, and how long an orphaned value is kept — and confusing them produces both needless requests and needless memory.

go deeper

for a junior

Recall that a cached entry is created on first use, trusted for a while, then still shown but rechecked, kept briefly after its last reader leaves, and finally dropped from memory.

for a middle

Be able to separate the two clocks out loud: the staleness window decides whether a read also refetches, the retention window decides how long an unsubscribed entry is kept. Name what resets each.

for a senior

Diagnose with them. Chatty app means windows and triggers; slow navigation despite a cache means retention or unstable keys; growing memory in a long session means an unbounded family of keys.

for a principal

Own the defaults and the exceptions. Decide which data classes deserve longer trust or longer retention, what the memory ceiling is on a low-end device, and how those choices are reviewed rather than rediscovered per feature.

## Why an entry needs a lifecycle at all A cache that only stored values would grow without limit and would never know when to check the server again. So each entry carries bookkeeping alongside the data: when the data arrived, how many components are currently subscribed to its key, and when the last of them went away. Those three facts drive every decision the cache makes. ## The stages | stage | what is true | reads served from memory? | refetches? | in memory? | |---|---|---|---|---| | **created / loading** | first request for this key is in flight, no value yet | no value to serve | the initial fetch | yes | | **fresh** | inside the staleness window | yes | no | yes | | **stale** | past the staleness window, still subscribed | yes | on a trigger | yes | | **unused** | last subscriber gone, value retained | yes, to a new subscriber | no | yes | | **collected** | retention window elapsed while unused | no, next read is a cold miss | n/a | no | The order is not strictly linear. An entry can be created and go stale before anything reads it again, or be unused and then picked up by a new subscriber, which puts it straight back into fresh or stale depending only on its arrival time. ## The two timers people conflate 1. **The staleness window** is about **trust**. It starts when the response arrives and decides whether a read also causes a refetch. It says nothing about memory. 2. **The retention window** is about **memory**. It starts when the subscriber count for the key drops to zero and decides how long an orphaned entry is kept before being dropped. They are independent, and each combination is meaningful: - short staleness, long retention — the common default. Navigating back paints instantly from the retained value, then corrects itself with one refetch. - long staleness, short retention — memory-lean but easy to get wrong: leave a screen briefly and the entry is gone, so the return trip is a cold load even though the value would still have been trusted. - zero staleness, long retention — every visit shows the retained value and then refetches. Correct, chatty, and the usual answer for data that changes often. - zero retention — the cache stops being a cache across navigation; it only deduplicates the readers alive at the same instant. ## Unused is not the same as invalid The most useful idea in this list is **unused**, sometimes reasoned about as an inactive entry. Nothing is wrong with the data; it simply has no reader. Keeping it is what makes back-navigation feel instant, and it is also why an entry can be refetched the moment a component mounts again rather than on a timer while nobody was looking. Two consequences follow: - **Nothing refetches an unused entry on its own.** Interval, focus and reconnect refetching are driven by subscribers; with no subscriber there is nobody to render a result, so polling an unused key spends requests on nothing. An explicit mark, or a value written into the cache by something else, can still touch the entry — what does not happen is a scheduled request on its behalf. - **An unused entry can be arbitrarily old when it is picked up.** It is served anyway, then revalidated, which is exactly the stale-while-revalidate read. This is why a return visit shows old content for a moment — that is the design working, not a bug. ## What resets what - A landed response resets the arrival time, making the entry fresh again. - A new subscriber cancels the pending collection for that key. - An explicit invalidation marks the entry stale immediately; whether it refetches now depends on whether anyone is subscribed. Invalidating a key nobody reads should mark it, not fetch it. - Dropping an entry outright is different from invalidating it: invalidation keeps the value for stale-while-revalidate reads, while dropping it forces the next reader into a loading state. Prefer invalidation unless the stored value is genuinely unsafe to show. ## Operating it When an app feels chatty, look at the staleness window and the refetch triggers. When an app feels slow on every navigation despite a cache, look at the retention window and at key stability — a key that is not built deterministically from the request never matches an existing entry, so entries accumulate as one-shot misses and the cache appears to do nothing while consuming memory. When memory grows in a long session, look for unbounded key families: one entry per keystroke of a search box, or per scroll position, each retained for the full window.

  • Why should an entry survive for a while after its last reader unmounts?
    Because leaving a screen is usually temporary. Keeping the value means the return visit paints immediately from memory and then corrects itself with one refetch, instead of showing a cold loading state. The retention window bounds that generosity so a long session does not accumulate every entry the user has ever opened.
  • What is the difference between invalidating an entry and removing it?
    Invalidation marks the entry untrusted but keeps the value, so the next read still paints instantly and revalidates. Removal frees the value, so the next read is a cold miss with a loading state. Removal is for data that is unsafe to show at all, such as anything belonging to a user who just signed out.
  • Should an unused entry keep refetching on a timer?
    No. Refetching exists to update what is on screen, and an unused entry has no reader to render into, so the request buys nothing and still costs the server. The design is to leave it alone and revalidate when a subscriber appears, which is also why a returning screen shows old data for a moment.

saying these in an interview costs you the question

  • Uses one number for both staleness and retention
  • Thinks an unused entry has invalid or corrupted data
  • Expects background polling of keys nobody reads
  • Believes going stale frees the entry's memory
  • Removes entries where invalidating them would do
  • Assumes every key family is naturally bounded in size