A backend returns HTML with a `Last-Modified` header but no `Cache-Control` and no `Expires`. Users report seeing outdated pages. Explain what caches are permitted to do with such a response and how you would fix it.
answer
- no headers ≠ no caching
- LM factor ≈ 10% of (Date - Last-Modified)
- Last-Modified without max-age makes caching MORE aggressive
- browser caches cannot be purged — only CDNs can
- 404/410 are cacheable by default too
basics
~20 sWith no explicit lifetime, a cache may invent one heuristically — commonly ten percent of the time since Last-Modified. A page unchanged for a hundred days can therefore be cached for ten. The fix is to always send an explicit Cache-Control, such as no-cache for content that must be current.
solid answer
~60 sRFC 9111 permits a cache to assign a **heuristic freshness lifetime** when a response has a cacheable-by-default status code and no `max-age`, `s-maxage` or `Expires`. The widely implemented heuristic is the **LM factor**: roughly `0.1 × (Date - Last-Modified)`, on the theory that something unchanged for a long time is unlikely to change in the next moment. So a document last modified 100 days ago may legitimately be cached for around 10 days — by a browser and by every proxy and CDN on the path. That is exactly the reported symptom, and nothing is misbehaving: silence is interpreted as consent. Note that a response with **no validator at all** is more likely to be treated as non-cacheable, so paradoxically adding `Last-Modified` without a lifetime made caching *more* aggressive. The fix is to stop being silent. Send `Cache-Control: no-cache` for documents that must always be current (still cheap, since revalidation usually returns 304), or a deliberate short `max-age`. Set an explicit default at the framework or edge so no response ever leaves without a caching policy.
code
bash · 6 linescurl -sI https://example.org/page | grep -iE 'cache-control|expires|last-modified|age|date'
# Date: Mon, 12 Aug 2026 10:00:00 GMT
# Last-Modified: Fri, 01 May 2026 09:12:00 GMT
# Age: 86400
# (no Cache-Control, no Expires)go deeper
Know that omitting Cache-Control does not prevent caching and that caches may guess a lifetime from Last-Modified.
State the LM-factor rule and give the fix — always send an explicit Cache-Control such as no-cache for documents.
Diagnose from Age and Last-Modified, distinguish the purgeable CDN layer from unpurgeable browser copies, and put a default policy at the framework or edge.
Treat missing caching headers as a defect class: enforce an explicit default everywhere, test for it, and account for the unrecoverable tail of browser-stored copies in incident planning.
## Silence is not "do not cache" The single most useful sentence here: **an HTTP response with no caching headers is not uncacheable.** RFC 9111 allows a cache to store a response whose status code is cacheable by default — 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501 — and, if the response carries no explicit expiration, to *derive* a freshness lifetime by heuristic. Origins that emit nothing have delegated the decision to every cache between them and the user. ## The LM-factor heuristic The specification does not mandate an algorithm; it requires only that the heuristic be conservative and that caches not use one when the response has explicit expiry or is otherwise restricted. In practice essentially everyone implements a variant of the **LM factor**, inherited from RFC 2616's non-normative guidance: ``` freshness_lifetime = (Date - Last-Modified) × 0.1 ``` usually with a cap (commonly a day or a week). The reasoning is a crude stability prior: a resource that has not changed in months probably will not change in the next few hours. Implementations differ in the factor and the cap, which is itself a problem — the behaviour of your site varies by which cache a user sits behind. A related quirk: with **no** `Last-Modified` and no explicit lifetime, most caches have nothing to compute from and treat the response as immediately stale or non-cacheable. So the counterintuitive outcome is that adding `Last-Modified` to a response with no `Cache-Control` *increases* how long it may be cached. ## Diagnosing the reported symptom Work from the wire, not from theory: 1. `curl -I` the URL and read `Cache-Control`, `Expires`, `Last-Modified` and `Age`. Absent lifetime plus a distant `Last-Modified` confirms the hypothesis. 2. A large `Age` on responses through the CDN tells you the edge is holding it and gives you the elapsed time. 3. Compare through the CDN versus straight to origin to locate which layer is serving stale. 4. Check whether affected users clear up with a hard reload — that indicates a browser-side stored copy, which you cannot purge and which will keep affecting them until its heuristic lifetime expires. That last point is the reason to treat this as urgent: a CDN can be purged, browser caches cannot. If a heuristic gave browsers ten days, some users will stay stale for ten days no matter what you deploy — unless the URL changes. ## The fix **Immediately:** ship an explicit `Cache-Control` on the affected responses. For HTML that must always reflect current state, `Cache-Control: no-cache` is usually right — it permits storage and requires revalidation, so with a validator present most requests cost a 304 with no body. If a small drift window is acceptable, a deliberate `max-age=60` is cheaper still. Then purge the CDN, and accept that already-cached browser copies will age out on their own. **Structurally:** make "no caching policy" impossible. Set a default `Cache-Control` in the framework or reverse proxy so every response carries one; make the edge override missing headers rather than pass silence through; and add a smoke test that asserts key routes emit the expected directives, since caching headers are easy to lose in a refactor and the failure is invisible until users complain. ## Two footnotes worth knowing Under RFC 2616, a cache that applied a heuristic lifetime greater than 24 hours had to attach a `Warning: 113 Heuristic Expiration` header. **RFC 9111 deprecates the `Warning` header entirely**, so that signal no longer exists and you cannot detect heuristic caching from response headers alone. Also note that `404` and `410` are cacheable by default. A transient 404 emitted during a deploy, with no `Cache-Control`, can be heuristically cached and keep a page "missing" long after it exists again — the same failure mode, on a status code most people never think to set caching headers for.
- Why does adding `Last-Modified` to a response with no Cache-Control make caching more aggressive rather than less?The LM-factor heuristic needs a `Last-Modified` value to compute from; without one, most caches have no basis for a heuristic lifetime and treat the response as immediately stale or non-cacheable. Supplying `Last-Modified` hands them the input, and the older the timestamp the longer the derived lifetime. The header is a validator, not a caching restriction.
- After you fix the headers and purge the CDN, why might some users still see the old page?Because their browser stored the response under the earlier heuristic lifetime, and there is no mechanism to purge a browser cache remotely. Those clients will not request the resource again until their stored copy goes stale. The only ways through are waiting it out or changing the URL so the stored entry is never consulted.
- Can you tell from a response's headers that a cache applied a heuristic lifetime?Not reliably any more. RFC 2616 required a `Warning: 113 Heuristic Expiration` header for heuristic lifetimes over 24 hours, but RFC 9111 deprecates the Warning header and caches no longer emit it. You have to infer it from the absence of Cache-Control and Expires combined with a non-zero Age on the response.
saying these in an interview costs you the question
- Assuming a response with no caching headers will not be cached
- Believing heuristic freshness is a bug in the CDN rather than specified behaviour
- Thinking a CDN purge fixes copies already stored in browsers
- Not realising 404 responses are cacheable by default and can be heuristically cached
- Relying on a Warning header to detect heuristic caching — it is deprecated