skip to content

Your team wants a service worker to cache authenticated API responses in Cache Storage so the app works offline. How would you decide whether that is acceptable, and what would you require before approving it?

level: principalimportance: nice to knowfreq 28%

answer

  1. origin-scoped, not user-scoped
  2. the key is the URL, not the header
  3. nothing ever expires by itself
  4. name the code path that deletes
  5. tolerant reads yes, money no

basics

~20 s

Weigh the offline benefit against storing per-user data on disk under an origin-wide, unencrypted namespace keyed by URL. Approve it only for read data whose staleness is harmless, with per-user cache names, a hard clear on logout and account switch, and a bounded, audited entry set.

solid answer

~50 s

The decision is a data-classification question, not a caching one. Cache Storage is per-origin, not per-user: entries are keyed by request URL, they never expire on their own, and every script on the origin can read them. So caching an authenticated response writes one user's data to a shared, long-lived namespace on a device that may be shared too. I would ask three things. Which data class is it — a tolerant read like a catalogue or a feed is very different from balances, entitlements or anything the user just wrote. What is the blast radius if a second user signs in on that device, or if the first user signs out and the entries survive? And does the cache key actually distinguish users, given that matching ignores request headers unless the response declares `Vary`? I would approve the tolerant reads with a per-user cache name, an explicit `caches.delete()` on logout and account switch, a capped entry count, and nothing personal or high-value stored at all.

go deeper

for a junior

Know that Cache Storage belongs to the origin rather than to a signed-in user, and that anything stored there stays until code removes it.

for a middle

Explain why the URL-keyed lookup does not separate accounts and what a logout path has to do about it, and write the cleanup that deletes the right caches.

for a senior

Classify the data before choosing a strategy, prescribe per-user cache names, an endpoint allowlist and a bounded entry count, and know that eviction is not a deletion guarantee.

for a principal

Own the policy across the product: which data classes may be cached at all, how account switching and forced sign-out tear storage down, how the footprint is bounded and reviewed, and when the honest answer is a shell-only offline experience.

## Why this is a policy question Any competent engineer can write a stale-while-revalidate handler for `/api/orders`. The lead's job is deciding whether that data belongs in Cache Storage at all, because the storage has properties the strategy discussion tends to skip. **It is origin-scoped, not user-scoped.** `caches.open(name)` opens a cache belonging to the origin. There is no notion of "the current user"; if two people use the same browser profile, or one signs out and another signs in, the second inherits whatever the first left behind unless you removed it. **It is keyed by request, and matching ignores headers by default.** A cache lookup matches on URL and method. Request headers — including `Authorization` — do not participate unless the *stored response* carries a `Vary` header naming them, and even then callers can pass `ignoreVary`. Two users hitting the same endpoint therefore collide on the same key unless you separate them yourself. **It does not expire.** Nothing in the platform ages an entry out. Whatever you write stays until your code deletes it or the browser evicts the origin's storage wholesale under pressure — which is not a privacy control you can rely on and not one you can schedule. **It is readable by the origin's scripts.** It is ordinary same-origin storage, not a vault. A script injection on the origin can enumerate it. That does not make caching wrong; it means the cached set should not be more sensitive than what the page already exposes. ## The questions I would ask 1. **What data class is it?** Tolerant reads — reference data, catalogues, a settings blob, an already-published feed — are cheap to cache and harmless when a load behind. Money, entitlements, medical or legal content, and anything reflecting a write the user just made are not, because a stale or leaked copy is a real incident rather than a cosmetic one. 2. **What is the multi-user story?** Shared laptops, kiosks and family tablets are the failure mode. If the product has account switching, the cache must be part of the switch. 3. **How does a user's data leave?** Logout must delete it, and "logout" includes session expiry and forced sign-out from the server, not just the button. If you cannot name the code path that deletes, the answer is no. 4. **How large can it get?** Every cached response competes for the origin's quota with things that matter more, and unbounded growth invites the browser to evict the whole origin. ## What I would require - **Per-user cache names**, e.g. `api-${userId}-v3`, so a key collision between accounts is impossible by construction rather than by discipline. - **An explicit teardown**: on logout and account switch, delete those caches outright. ```js async function clearUserCaches(userId) { const names = await caches.keys(); await Promise.all( names.filter((n) => n.startsWith(`api-${userId}-`)).map((n) => caches.delete(n)) ); } ``` - **An allowlist of endpoints**, not a wildcard route. New endpoints are cached by decision, not by default. - **A cap** on entries with oldest-out trimming, so the footprint is bounded and reviewable. - **Nothing high-value stored**, and no offline *writes* smuggled in under this banner — queued mutations are a different design with different failure modes. ## The answer I would give Yes for tolerant reads under those controls; no for anything where a stale or inherited copy is a real harm. And I would push back on the framing: "works offline" usually means *the app opens and shows something honest offline*, which the precached shell plus a clear offline state already delivers. Caching the user's data is a separate decision that should be justified on its own.

  • Why does a service worker cache lookup not distinguish two users hitting the same endpoint?
    Cache matching is keyed on the request URL and method. Request headers, including `Authorization`, are not part of the key unless the stored response carries a `Vary` header naming them — and callers can even pass `ignoreVary` to skip that. Separating users therefore has to be done deliberately, most simply by putting each user's data in its own cache name.
  • Someone argues the browser will evict old entries anyway, so logout cleanup is unnecessary. Your response?
    Eviction is a storage-pressure mechanism, not a privacy control: it is undated, unpredictable, and typically clears an origin wholesale rather than one user's entries. Treating it as a deletion guarantee means a second user on the device can read the first user's data for an unbounded period. Deletion must be an explicit code path you can point to.
  • Would you cache authenticated responses differently for a kiosk or shared-device product?
    I would not cache them at all. On a shared device the population of the cache and the population of the current session are unrelated, and the offline benefit is near zero because kiosks are usually wired. The precached shell plus a clear offline state gives the resilience without putting one user's data where the next one is standing.

saying these in an interview costs you the question

  • Treats Cache Storage as private to the logged-in user
  • Assumes the Authorization header separates cache entries
  • Relies on browser eviction instead of deleting on logout
  • Caches everything under /api/ with one wildcard route
  • Conflates offline reads with queuing offline writes

context