skip to content

In a service worker, what does the stale-while-revalidate caching strategy do when a request comes in, and what does that mean for what the user actually sees on screen?

level: juniorimportance: should knowfreq 52%

answer

  1. two tracks from one request
  2. the page gets the old copy
  3. fresh bytes land for next time
  4. keep the background fetch alive
  5. clone before you store

basics

~20 s

It answers the request from the cache straight away and, in parallel, fetches from the network and stores the fresh copy. The user sees the previous version instantly; the new data appears only on the next request, unless the app re-renders when the update lands.

solid answer

~50 s

On each request the worker does two things at once. It looks the request up in Cache Storage and returns the hit immediately, so the page renders without waiting for the network. Separately it starts a `fetch` for the same request and writes the result back into the cache. The response the page received is the *old* one — the fresh bytes sit in storage waiting for the next visit. That is the trade: near-zero latency and offline resilience, in exchange for content that can be one load behind. If nothing is cached yet, the strategy has to fall back to the network response, so the very first request is a plain network request. Because the revalidation must outlive the response, you keep it alive with `event.waitUntil()`, and you clone the response before storing it since a body can only be read once.

go deeper

for a junior

Be able to say plainly that the cached copy goes to the page now and the fetched copy is saved for next time, and give one resource where a load of lag is fine.

for a middle

Explain the mechanics: the parallel fetch, waitUntil() keeping the worker alive for the write, clone() because a body reads once, and the first-request fallback path.

for a senior

Show judgment about which resources tolerate one-load staleness, and describe how you surface an update to the page rather than letting stale content sit silently.

for a principal

Own the policy: define per data class how much lag is acceptable, and make the refresh signal a shared platform behaviour rather than something each feature reinvents.

## The two halves of one request Stale-while-revalidate is the strategy that deliberately hands the user something slightly out of date in order to hand it to them instantly. In a service worker's `fetch` handler it looks like this: ```js async function swr(event) { const cache = await caches.open('content-v1'); const cached = await cache.match(event.request); const fresh = fetch(event.request).then((response) => { if (response.ok) cache.put(event.request, response.clone()); return response; }); event.waitUntil(fresh.catch(() => {})); return cached || fresh; } ``` Read it as two independent tracks. The **serve** track resolves `respondWith` from storage as soon as the lookup completes — typically single-digit milliseconds, with no network involved. The **revalidate** track runs a normal `fetch` and writes the result into the cache. The page is never told the second track happened. ## What the user sees First ever visit: nothing is cached, so `cached` is undefined and the strategy degrades to a plain network request. The user waits for the network exactly once. Every later visit: instant render of what was stored last time. If the server has changed the resource since, the user is looking at the previous version. The new bytes land in the cache while they read, and appear the *next* time the resource is requested. This is why it suits avatars, feeds, product listings and dashboards, and not balances, permissions or anything the user just submitted. Offline: the cache hit still resolves, so the page renders normally and only the background fetch fails — which is why the revalidation is wrapped in a `catch` that swallows the error. Without it an offline visit produces an unhandled rejection for every request. ## The two mechanical details people miss **`event.waitUntil()`** — once you resolve `respondWith`, the browser is free to consider the fetch event done and may shut the worker down. Passing the revalidation promise to `waitUntil` tells the browser the event is still doing useful work, so the write to the cache actually completes. **`response.clone()`** — a `Response` body is a stream that can be consumed once. If the same response object is both returned to the page and passed to `cache.put()`, one consumer finds the body already used. Clone first, then hand out both. ## Making the staleness visible Being one load behind is acceptable only if the UI is honest about it. Two common refinements: broadcast a message to the page when the revalidation stores something different so the app can re-render or show a "new content available" affordance, or reserve the strategy for data where a beat of lag is invisible. If neither is true for your resource, stale-while-revalidate is the wrong strategy and network-first is the honest one.

  • What happens on the very first request for a resource under stale-while-revalidate?
    There is no cache entry, so the strategy has nothing stale to serve and must return the network promise instead. The user pays full network latency once, and the response is stored on the way out so every later request can be answered instantly.
  • Why wrap the background fetch in event.waitUntil()?
    Once `respondWith` resolves, the browser may terminate an idle service worker at any moment. `waitUntil()` extends the event's lifetime until the promise settles, so the `cache.put()` actually finishes. Without it the revalidation can be killed mid-write and the cache never updates.
  • How would you let the page know that a newer version arrived in the cache?
    Post a message from the worker after the revalidation stores a changed response — for example iterate `clients.matchAll()` and call `client.postMessage(...)`. The page listens on `navigator.serviceWorker` for `message` and re-renders or shows a refresh affordance, which turns invisible staleness into a deliberate UX choice.

saying these in an interview costs you the question

  • Says the user gets the fresh network response on this request
  • Forgets the first request has nothing to serve stale
  • Stores the response without cloning it first
  • Assumes the background fetch always completes without waitUntil
  • Uses it for data where being one load behind is unsafe

context