HomeJournalThis post

Foundations: HTTP caching without superstition

Cache contracts make response selection, freshness, validation, privacy, mutation, stale behavior, and deployed-path evidence explainable.

JP
JP Casabianca
Designer/Engineer · Bogotá

A cache is a stored response plus a rule for when that response may be reused.

The browser, a shared CDN, a reverse proxy, a service worker, and application memory can all reuse data, but they do not share the same trust boundary or freshness policy. Saying something is cached does not identify which layer acted or why.

RFC 9111 defines HTTP cache storage, freshness, validation, invalidation, and the difference between private and shared caches. Its vocabulary turns a vague performance trick into an inspectable contract.

We will build that contract from the representation outward: who may store it, which request selects it, how long it is fresh, how it is validated, and what happens after a mutation.

Caching becomes manageable when every reuse decision can be explained from response metadata.

01 · SelectFind a matching response

Method, target URI, Vary fields, partitioning, and cache key determine whether a stored response is eligible.

02 · EvaluateReuse or revalidate

Fresh responses can be reused; stale responses need permission, validation, or an explicit disconnected behavior.

03 · UpdateStore the new result

A 304 refreshes metadata; a new 200 replaces the representation; unsafe methods invalidate related targets.

Figure 1: A cache decision moves from selection to freshness to validation.

Inventory every cache layer

Start by locating browser caches, shared CDNs, reverse proxies, service workers, framework data caches, and in-process memoization because each has different controls.

I would pressure-test that decision with four questions:

  • Which layer can store this response?
  • Which layer served the observed request?
  • Does deployment add an implicit cache?
  • Can evidence distinguish a hit from an origin response?

The failure mode here is debugging the browser while a CDN supplies the stale representation. In web applications, APIs, CDNs, browsers, service workers, and authenticated product surfaces where freshness, reuse, validation, privacy, and invalidation are often reduced to folklore about hard refreshes, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a cache-layer topology. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is a known owner and inspection path for every reuse layer. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a cache-layer topology beside the question “Which layer can store this response?” before the first implementation review. The next pass would use “Which layer served the observed request?” to test the boundary, then “Does deployment add an implicit cache?” to expose the state most likely to be missed. I would keep “Can evidence distinguish a hit from an origin response?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports a known owner and inspection path for every reuse layer.

Define the representation and key

A cache reuses a selected representation, so URL normalization, request headers, content negotiation, cookies, and application key construction must be explicit.

The practical review starts here:

  • Which URI identifies the resource?
  • Which request fields change bytes?
  • What belongs in Vary?
  • Can two users share the result?

Those questions keep adding arbitrary query strings as a universal cache bust from becoming the default. I would capture the decision in a response-selection and key table, then use it while the work is still cheap to change. For predictable HTTP response reuse, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like different representations no longer collide silently. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a response-selection and key table part of the working surface. I would use it to answer “Which URI identifies the resource?” while scope is still flexible, and “Which request fields change bytes?” before code or content becomes expensive to unwind. During QA, “What belongs in Vary?” and “Can two users share the result?” become concrete checks rather than discussion prompts. That sequence turns predictable HTTP response reuse into something the team can operate and gives me a specific outcome to report: different representations no longer collide silently.

  1. FreshNo origin round trip

    The stored response remains inside its freshness lifetime and policy permits reuse for this request.

  2. StaleAsk conditionally

    Send If-None-Match or If-Modified-Since; a validator can confirm the bytes still represent current state.

  3. ChangedReceive a representation

    The origin returns new content and validators, and each cache updates according to its own storage rules.

Figure 2: Freshness and validity are separate questions.

Separate storage from freshness

A response may be stored and still require validation on every use; no-cache permits storage while no-store forbids it.

Before implementation, I would answer:

  • May this response be retained?
  • How long is it fresh?
  • What happens when stale?
  • Is disconnected reuse allowed?

The artifact is a storage-and-freshness decision matrix. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is using no-cache and no-store interchangeably; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is policy language that matches the intended retention boundary. That connects a cache contract that names the stored response, cache key, freshness lifetime, validator, revalidation path, privacy boundary, and purge behavior to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “May this response be retained?” easy to answer. The boundary should force a decision about “How long is it fresh?” and “What happens when stale?.” I would record both in a storage-and-freshness decision matrix, including the part that stayed unresolved after the first pass. The final check, “Is disconnected reuse allowed?,” is where the artifact earns its place: it either supports policy language that matches the intended retention boundary, or it shows exactly why another iteration is needed.

Choose validators deliberately

Strong entity tags identify byte-equivalent representations while weak validators support semantic equivalence; Last-Modified has coarser constraints.

I would use these prompts during the working review:

  • Can the origin compute an ETag?
  • Does compression change the representation?
  • Is weak comparison sufficient?
  • Can modification time collide?

If the team slips into emitting decorative validators that never change or cannot be compared, the product can still look complete while its operating rule stays ambiguous. I would make a validator contract per response class the shared reference and keep it small enough to update as evidence changes.

The standard is conditional requests that correctly distinguish unchanged content. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a validator contract per response class, review it against “Can the origin compute an ETag?,” implement the narrowest useful path, and then return with evidence for “Does compression change the representation?.” I would use “Is weak comparison sufficient?” to inspect product consequence and “Can modification time collide?” to decide whether the result is stable enough to ship. This keeps emitting decorative validators that never change or cannot be compared visible as a known risk and makes conditional requests that correctly distinguish unchanged content the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
publicShared reuse allowedUseful for identical assets or documents; authorization and personalization still require deliberate keying.
privateUser-agent cache onlyA personalized response may be stored by the browser while shared caches must not retain it.
no-storeDo not storeAppropriate when retention itself is unsafe; it is not a synonym for always validate.
Figure 3: Cache-Control expresses different boundaries.

Model shared-cache privacy

Authenticated and personalized responses need a clear rule because a shared hit can become a data disclosure, not merely a freshness bug.

I would pressure-test that decision with four questions:

  • Does Authorization participate?
  • Is the response personalized?
  • Can the CDN prove tenant separation?
  • Should only a private cache store it?

The failure mode here is marking a response public to improve a dashboard metric. In web applications, APIs, CDNs, browsers, service workers, and authenticated product surfaces where freshness, reuse, validation, privacy, and invalidation are often reduced to folklore about hard refreshes, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an authenticated-response threat model. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is reuse that never crosses identity or tenant boundaries. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an authenticated-response threat model beside the question “Does Authorization participate?” before the first implementation review. The next pass would use “Is the response personalized?” to test the boundary, then “Can the CDN prove tenant separation?” to expose the state most likely to be missed. I would keep “Should only a private cache store it?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports reuse that never crosses identity or tenant boundaries.

Connect mutation to invalidation

Unsafe requests can invalidate cached targets, but product architectures often require explicit purges, URL versioning, or bounded staleness for related resources.

The practical review starts here:

  • Which resource changed?
  • Which derived views are affected?
  • Can the URL be content-addressed?
  • How long may propagation take?

Those questions keep trying to enumerate every cached page after every write from becoming the default. I would capture the decision in a mutation-to-cache invalidation map, then use it while the work is still cheap to change. For predictable HTTP response reuse, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like bounded and observable propagation of changed state. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a mutation-to-cache invalidation map part of the working surface. I would use it to answer “Which resource changed?” while scope is still flexible, and “Which derived views are affected?” before code or content becomes expensive to unwind. During QA, “Can the URL be content-addressed?” and “How long may propagation take?” become concrete checks rather than discussion prompts. That sequence turns predictable HTTP response reuse into something the team can operate and gives me a specific outcome to report: bounded and observable propagation of changed state.

Design stale behavior

stale-while-revalidate, stale-if-error, offline service-worker responses, and must-revalidate express different product promises during delay or failure.

Before implementation, I would answer:

  • May old data be shown?
  • Must staleness be labeled?
  • Which actions become unsafe?
  • How does recovery replace state?

The artifact is a stale-state product matrix. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is serving stale account data with no visible freshness context; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is useful degradation without presenting old state as current. That connects a cache contract that names the stored response, cache key, freshness lifetime, validator, revalidation path, privacy boundary, and purge behavior to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “May old data be shown?” easy to answer. The boundary should force a decision about “Must staleness be labeled?” and “Which actions become unsafe?.” I would record both in a stale-state product matrix, including the part that stayed unresolved after the first pass. The final check, “How does recovery replace state?,” is where the artifact earns its place: it either supports useful degradation without presenting old state as current, or it shows exactly why another iteration is needed.

Observe cache decisions

Age, Via, cache-status headers, server timing, request validators, response codes, and origin logs should make the actual decision reconstructable.

I would use these prompts during the working review:

  • Was this a hit?
  • How old was the response?
  • Which key matched?
  • Did the origin validate it?

If the team slips into inferring cache behavior only from perceived speed, the product can still look complete while its operating rule stays ambiguous. I would make a cache-debugging receipt template the shared reference and keep it small enough to update as evidence changes.

The standard is one trace that explains the complete reuse path. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a cache-debugging receipt template, review it against “Was this a hit?,” implement the narrowest useful path, and then return with evidence for “How old was the response?.” I would use “Which key matched?” to inspect product consequence and “Did the origin validate it?” to decide whether the result is stable enough to ship. This keeps inferring cache behavior only from perceived speed visible as a known risk and makes one trace that explains the complete reuse path the release receipt rather than a hopeful conclusion.

Test the deployed path

Verification should cover cold, fresh, stale, validated, changed, personalized, purged, disconnected, and error cases through the real CDN and browser.

I would pressure-test that decision with four questions:

  • Does a second request reuse?
  • Does 304 preserve headers?
  • Can identities contaminate?
  • Does mutation become visible on schedule?

The failure mode here is testing curl against localhost and assuming every intermediary agrees. In web applications, APIs, CDNs, browsers, service workers, and authenticated product surfaces where freshness, reuse, validation, privacy, and invalidation are often reduced to folklore about hard refreshes, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a deployed cache scenario suite. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is protocol and product behavior that holds through production layers. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a deployed cache scenario suite beside the question “Does a second request reuse?” before the first implementation review. The next pass would use “Does 304 preserve headers?” to test the boundary, then “Can identities contaminate?” to expose the state most likely to be missed. I would keep “Does mutation become visible on schedule?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports protocol and product behavior that holds through production layers.

Explain caching with receipts

A strong engineering narrative shows headers, request sequence, cache-status evidence, privacy decisions, changed latency, and the remaining invalidation boundary.

The practical review starts here:

  • What was reused?
  • Why was reuse legal?
  • Which failure changed the design?
  • What metric proves the improvement?

Those questions keep claiming a performance win without showing freshness or correctness from becoming the default. I would capture the decision in a redacted cache decision trace, then use it while the work is still cheap to change. For predictable HTTP response reuse, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like credible evidence of faster delivery within a defined truth boundary. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a redacted cache decision trace part of the working surface. I would use it to answer “What was reused?” while scope is still flexible, and “Why was reuse legal?” before code or content becomes expensive to unwind. During QA, “Which failure changed the design?” and “What metric proves the improvement?” become concrete checks rather than discussion prompts. That sequence turns predictable HTTP response reuse into something the team can operate and gives me a specific outcome to report: credible evidence of faster delivery within a defined truth boundary.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a cache-layer topology
  • a response-selection and key table
  • a storage-and-freshness decision matrix
  • a validator contract per response class
  • an authenticated-response threat model

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because a cache contract that names the stored response, cache key, freshness lifetime, validator, revalidation path, privacy boundary, and purge behavior becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

http-cache-contract.md
# asset
public, max-age=31536000, immutable
Content-hashed URL changes when bytes change; long reuse has no purge dependency.

# document max-age=0, must-revalidate; ETag Browser stores the response but validates before reuse, often receiving a small 304.

# account private, no-cache; Vary: Accept-Encoding Browser may retain it but must validate; shared storage is disallowed and user identity is not a Vary patch.

Figure 4: One response should carry an explainable policy.

Resource path

The practical follow-up I would build is an HTTP cache-contract worksheet with response classes, Cache-Control directives, Vary dimensions, validators, CDN behavior, authenticated cases, invalidation triggers, observability, and browser tests. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Which layer can store this response?
  • Which URI identifies the resource?
  • May this response be retained?
  • Can the origin compute an ETag?
  • Does Authorization participate?
  • Which resource changed?
  • May old data be shown?
  • Was this a hit?
  • Does a second request reuse?
  • What was reused?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For predictable HTTP response reuse, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • bounded and observable propagation of changed state
  • useful degradation without presenting old state as current
  • one trace that explains the complete reuse path
  • protocol and product behavior that holds through production layers
  • credible evidence of faster delivery within a defined truth boundary

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • A cache decision moves from selection to freshness to validation.
  • Freshness and validity are separate questions.
  • Cache-Control expresses different boundaries.
  • One response should carry an explainable policy.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to web applications, APIs, CDNs, browsers, service workers, and authenticated product surfaces where freshness, reuse, validation, privacy, and invalidation are often reduced to folklore about hard refreshes: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through a cache contract that names the stored response, cache key, freshness lifetime, validator, revalidation path, privacy boundary, and purge behavior. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

A precise caching model is a hiring signal because it shows I can reason across protocol semantics, infrastructure, product freshness, and debugging evidence instead of treating cache behavior as magic.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
TemplateJul 2026

Dependency Adoption Receipt

A reviewable receipt for package need, identity, provenance, permissions, supply-chain risk, verification, ownership, and removal.

Supply chainSecurityAI-assisted
View details