Caching is the easiest place to introduce a silent correctness bug, because a cache looks correct right up until it serves bytes that do not match the origin. The mental model that keeps this straight treats every cache as decoding two independent clocks instead of one expiry value.

A conditional request carries two kinds of information, and conflating them is how stale content ships. One clock is freshness — how long a response may be reused without asking. The other is validation — how a client asks whether its copy is still current. This essay reads both clocks through this site’s live response and shows what each one is actually for.

Freshness: how long to trust a byte without asking

Freshness is the clock that decides whether a request is served from the cache at all. It is normally expressed as Cache-Control (max-age) or an Expires date. Within the freshness window, a cache returns the stored response and never contacts the origin. Its job is to reduce latency and origin load; its cost is that it can serve a version slightly behind the origin within the window.

For a static site the freshness window and the immutable artifact agree: a deploy replaces the whole tree, so a short freshness plus a validator is a safe, boring default. The important thing is that freshness alone cannot detect stale content after the window expires — that is the second clock’s job.

Validation: how a client asks “is it still current?”

When freshness expires, the client does not necessarily re-download the whole resource. Validation is the conditional request: send the stored identifier, and the origin replies either with the new representation or with a status saying the stored copy is still current. The two identifiers HTTP defines are the ETag (an opaque validator, typically derived from entity bytes) and Last-Modified (a timestamp validator).

The live response of this site shows both clocks concretely:

etag: "dkx84jwth7nkc9e"
last-modified: Mon, 24 Aug 2026 00:00:00 GMT

A conditional If-None-Match request against that ETag yields a 304 Not Modified — the validators agree, so the client reuses the cached copy without re-downloading the body. This is the second clock doing its job: a cheap round trip, not a full transfer.

Why freshness and validation are independent

The two clocks must be understood separately because one does not imply the other. A response can be freshly cached and need no validation, yet the validation identifiers still matter for the moment freshness lapses. Conversely you can have strong validators but a freshness policy that causes needless revalidation for resources that barely change. For a static file the two are easy to set coherently: a short or public freshness, a strong ETag from the artifact bytes, and a revalidation path the client uses the moment freshness passes.

The failure mode to watch is collapsing both into “the cache expired, so re-download”. That ignores validators and turns every expiry into a full transfer. The compressed artifact makes the round trip small — this page’s HTML transfers as a few kilobytes — but the principle (the validators principle (a cache refuses stale but avoids redundant whole-body transfer) holds regardless of size.

The contract worth asserting

Once you can name freshness and validation separately, a release check can assert them: the served headers include both etag and last-modified; the freshness window is short enough to be honest; and a conditional request returns 304 when the validator matches. Those three checks describe a cache that cannot silently serve indefinitely-old bytes or leak bandwidth. That is a weaker word to prove than dashboard graphs, which is why it belongs in a decision log rather than in a monitoring chart.

Sources