skip to content

A user reports seeing another account's data on a page; how do you work out which cached copy served that response?

level: seniorimportance: must knowfreq 55%

answer

  1. scope failure, not a freshness failure
  2. two identities, one URL
  3. private window rules out the browser
  4. age headers and a latency cliff
  5. fix placement, not the key

basics

~20 s

Treat a wrong-account report as an exposure, not staleness: reproduce with two identities against a production-shaped build, then eliminate placements one at a time, using age or debug headers and a latency cliff as evidence.

solid answer

~50 s

A wrong name is a scope failure: the response was produced for one identity and handed to another. Reproduce deliberately - sign in as A, load the URL to warm whatever copy exists, then request the same URL as B in a fresh private window. If B sees A's data there, the browser's private copy is ruled out and something that outlives the request is holding it. Distinguish a stored copy from a fresh render using any age or debug header the stack emits, a response far faster than the origin's normal latency, and the test of changing the source data and seeing the response not move. Check whether the copy is a rendered route or a data read by seeing whether the personal string is in the initial HTML. Contain it by taking that read or route out of the shared copy, then fix placement rather than the key.

go deeper

for a junior

Know the tell: seeing someone else's data means a response was reused across people, which is different from seeing your own out-of-date data.

for a middle

Practise the elimination sequence - warm as one identity, request as another in a clean browser - and be able to say what each outcome rules out.

for a senior

Show production instinct: reproduce against a production-shaped build, corroborate with two signals before concluding, contain before fixing, and add the two-identity regression test.

for a principal

Treat it as an incident with a class, not an instance: after containment, sweep for the same placement decision elsewhere and decide what makes it structurally hard to repeat.

## First, name the failure class A user seeing *their own* stale data is a freshness problem. A user seeing *someone else's* data is a **scope** problem: a response produced in the context of one identity was handed to another. That distinction changes the urgency, the people you involve, and where you look. Freshness bugs live in windows and invalidation; scope bugs live in placement and keys. It also sets an expectation about the symptom's shape. Scope failures are intermittent and traffic-dependent - they need a second request to arrive while a copy from the first is still around - and they usually cannot be reproduced in a development server, where placements that store output are often inactive. ## Reproduce it on purpose Guessing from a screenshot wastes the outage. Build the smallest experiment that can produce the bug: 1. Run against a **production-shaped build**, not a development server, so the stored placements actually exist. 2. Sign in as identity **A**, load the URL, and confirm A's data. This warms whatever copy exists. 3. In a **fresh private window** with no shared storage, sign in as identity **B** and request the same URL. 4. Repeat with B first, then A, to see whether the direction matters. 5. Repeat while signed out entirely - if an anonymous request receives personal data, the copy is fully public and the severity goes up. The private window is doing real work here: it proves the copy is not coming from the reporter's own browser. ## Eliminate placements one at a time | Observation | Placement it implicates | | --- | --- | | Only the original browser shows it; a private window does not | that browser's own payload or history copy | | Wrong only after an in-app navigation, right after a full load | the client-side route payload for already-visited routes | | A second, freshly signed-in browser sees it too | a copy on the server that outlives the request | | An anonymous request sees it | a shared copy keyed without identity, or a layer in front of the origin | | It follows one instance and not another | a copy held in that instance's memory | | It disappears permanently after the stored copy is removed | stored render output rather than a live render bug | Work down the list rather than forming a theory first. Each row is one cheap experiment. ## Signals that a stored copy answered - **Age or debug headers**, where the framework or the host emits them - the most direct evidence available. - **A latency cliff**: a response returning in a millisecond or two when the render normally costs tens or hundreds is not doing the work. - **Data that will not move**: change the underlying record and reload; a response that ignores the change is not being produced from it. - **Byte-identical responses across identities**: if two different signed-in users receive the same bytes including a personal string, one stored copy is answering both. - **A timing pattern**: the wrong data appears only within a window after someone else's visit, then stops - the shape of a copy expiring. Be careful not to over-read a single signal. A fast response can also mean a warm database; a header can be added by something in front of your app. Confirm with at least two. ## Is it a rendered route or a data read? View the response the server actually sent, not the page after client-side code runs. If the other account's string is already in the initial HTML, a rendered route was stored. If the HTML is generic and the wrong data appears afterwards, the copy is a data read reused during the render, or something the client fetched and reused. That determines what you have to remove, and it usually tells you which read is responsible. ## Contain, then fix properly Containment is the immediate move: take that route or read out of the shared copy so every request renders it, and remove the copies already stored. Expect a load increase and watch for it. Then do the actual fix, which is almost always **placement, not keys**: - mark the read as request-bound so it can never be placed in something that outlives the request; - or split the response, keeping the personalised part out of the shared copy while the rest stays reusable; - and add a regression test that performs the two-identity sequence against a production-shaped build, since the bug is invisible to a single-identity test. It is worth finishing the investigation even after the symptom stops. A copy that leaked once was placed wrongly; the same placement decision is usually repeated in several other routes written by the same hand at the same time, and a quick sweep for reads of identity inside anything marked cacheable tends to find them.

  • How do you reproduce a leak that only appears under real traffic?
    Recreate the condition it needs: a copy warmed by one identity and a second request arriving while it is still around. Run against a production-shaped build, request as A, then immediately as B in a clean browser with the same URL, and alternate. If the window is short, drive it with a small loop rather than by hand.
  • What do you do first - remove the stored copies or ship the code fix?
    Contain first: stop the route or read being served from a shared copy, then remove the copies already stored, so nothing further is exposed while you work. Watch the load that this adds. The code fix that changes placement follows, along with a two-identity regression test, because removing copies alone leaves the same bug ready to recur.
  • The response is fast and carries no age header. Is that enough to conclude a stored copy answered?
    No. Low latency can also come from a warm database or a cheap render, and many stacks emit no age header at all. Corroborate with a second signal - change the underlying data and see whether the response moves, or compare bytes across two identities - before you act on the conclusion.

saying these in an interview costs you the question

  • Calls a wrong-account report a staleness bug
  • Debugs it in a development server where stored copies do not exist
  • Blames the browser cache without testing a fresh private window
  • Treats a fast response alone as proof a stored copy answered
  • Removes the stored copies and ships nothing that changes placement
  • Fixes it by adding identity to the key and stops there