What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load
A conditional request works by having the client echo back a validator it was given earlier — an opaque ETag token or a Last-Modified timestamp — so the origin can compare that validator against the resource's current version and, when they match, answer with a bodyless 304 Not Modified that tells the client to reuse bytes it already holds instead of re-transmitting them.
The server first hands out a validator alongside the full 200 OK. The browser stores it with the cached bytes. On the next fetch the browser turns an ordinary GET into a conditional GET by attaching the saved validator in a precondition header:
If-None-Match: "a1b2"— "send the body only if the current ETag is nota1b2."If-Modified-Since: Tue, 15 Sep 2025 11:00:00 GMT— "send the body only if it changed after this instant."
The server evaluates the precondition. Unchanged → 304, no body. Changed → 200 with the fresh body and a new validator. The check itself is cheap; the win is that unchanged responses skip the payload entirely.
A worked trace, with real bytes
Suppose /styles.css is 48 KB and the origin sets Cache-Control: max-age=60. Follow one browser across three visits:
| # | What the browser sends | Server decision | On the wire |
|---|---|---|---|
| 1 — cold | GET /styles.css (no validators) | Serve fresh copy | 200 OK + 48 KB body + ETag: "a1b2", Last-Modified: Tue, 15 Sep 2025 11:00:00 GMT |
| 2 — within 60 s | Nothing — entry is still fresh | Not consulted | 0 bytes. Served straight from cache; no request leaves the machine |
| 3a — stale, unchanged | If-None-Match: "a1b2" | Current ETag is still "a1b2" → match | 304 Not Modified, ~180 bytes of headers, no body. ~99.6% of the transfer avoided |
| 3b — stale, changed | If-None-Match: "a1b2" | Current ETag is now "c3d4" → mismatch | 200 OK + new 48 KB body + ETag: "c3d4". Cache is replaced |
The crucial subtlety the naive mental model misses: visit 2 sends nothing, but visits 3a/3b both pay one full round-trip. A 304 collapses the payload, not the request. On a 40 ms link, a revalidation that returns 304 still blocks that resource for ~40 ms — cheap versus 48 KB, but not free. That distinction drives the whole selection story below.
ETag vs Last-Modified, and strong vs weak
An ETag is an opaque token the server picks to name a specific representation — usually a hash of the bytes or a version number. It carries a strength flag:
- Strong —
ETag: "a1b2"— promises the bytes are identical octet-for-octet. Required for byte-range resumption (If-Range). - Weak —
ETag: W/"a1b2"— promises only that the representations are semantically equivalent. Use it when trivial byte differences (whitespace, a regenerated-but-identical file, gzip vs identity) shouldn't count as a change.
Last-Modified is a wall-clock timestamp. It is a weak validator by nature: HTTP-date has one-second resolution, so two edits in the same second look unchanged, and it depends on the server's clock and filesystem mtime being trustworthy. It also can't tell "rewritten with identical content" from "unchanged."
Both are used together in practice — browsers commonly send If-None-Match and If-Modified-Since in the same request, and per RFC 9110 the server evaluates If-None-Match first and only falls back to the date when no ETag is present.
| Dimension | ETag | Last-Modified |
|---|---|---|
| Basis | Content fingerprint / version chosen by server | Filesystem mtime / clock |
| Precision | Byte-exact (strong) or semantic (weak) | 1 second; blind to sub-second edits |
| Comparison | Exact token match | Date comparison |
| Write header | If-None-Match / If-Match | If-Modified-Since / If-Unmodified-Since |
| Cost | Server must compute/store the tag | Free — mtime already exists |
| Best for | Dynamic bodies, APIs, CDNs, concurrency control | Static files with reliable mtime, single origin |
How this fits with Cache-Control
Conditional requests are only half of HTTP caching. Cache-Control governs freshness; validators govern revalidation. They compose:
- While fresh (age <
max-age), the browser uses the cached copy with no request at all — the fastest possible outcome (visit 2 above). - Once stale, the browser doesn't discard the copy; it revalidates it with a conditional request, hoping for a 304 to keep reusing it.
Two directives change when that revalidation happens:
Cache-Control: no-cachemeans "you may store it, but revalidate before every use" — it forces a conditional request each time (it does not mean "don't cache"; that'sno-store).Cache-Control: must-revalidatemeans "once stale, you may not serve it without a successful revalidation" (no serving-stale-on-error).
So the levers are: raise max-age to eliminate requests entirely during the freshness window; rely on validators to stay correct once stale. The 304's residual round-trip cost is exactly why a long max-age is worth reaching for when you can tolerate a brief staleness window.
Pitfalls
- Treating 304 as "free." Every revalidation is a round-trip. A page with 60 stale sub-resources fires 60 conditional requests on reload; on a high-latency link that's a visible stall even though almost no bytes move. If content rarely changes, raise
max-ageor use fingerprinted URLs instead of leaning on 304s. - Inode-based ETags behind a load balancer. Apache's default
FileETaghistorically mixed in the file's inode number. The same file on two origin servers then produces two different ETags, so a client that hit server A and later lands on server B always gets a 200 — the cache never validates. Fix:FileETag MTime Size(dropINode), or set ETags from a content hash. - Compression vs a strong ETag. If you hash the uncompressed body but serve gzip/br, the ETag no longer names the bytes on the wire. Use a weak ETag for content-negotiated representations, vary correctly on
Accept-Encoding, and ensure the tag reflects the representation actually sent. - Weak ETag where a strong one is required. Byte-range requests (
If-Range, video seeking, resumable downloads) need a strong validator. A weak ETag silently disables range resumption — the client re-downloads from zero. - Proxies/CDNs dropping precondition headers. If an intermediary doesn't forward
If-None-Match, the origin can never answer 304 and always sends the full body. Verify end-to-end, not just against the origin. - Last-Modified clock traps. One-second resolution hides rapid edits, and a mtime in the future (bad clock, restored backup) can make a changed file look unchanged forever. Prefer ETags when edits can be frequent or clocks are shaky.
When to use it — and when not
Reach for conditional requests when the URL is stable and can't carry a version — HTML documents, user-generated content, API responses — and you need correctness on change without re-transmitting unchanged bytes. Signals that point here: the resource changes unpredictably; the same URL must always serve the latest; you're behind a CDN or API gateway that keys on ETags.
Pick ETag when changes are frequent, sub-second, or content-based, or when you also want optimistic concurrency (If-Match on writes to reject lost updates). Pick Last-Modified for static files on a single origin where mtime is trustworthy and you want validation for free.
Trade-off vs the main alternative: immutable + fingerprinted URLs
For build artifacts, the stronger pattern is content-fingerprinted URLs — app.9f3c1a.css served with Cache-Control: max-age=31536000, immutable. The browser never revalidates; a change ships as a new URL referenced by freshly built HTML.
- What you gain over conditional requests: zero revalidation traffic — not even a 304 round-trip — for the life of the asset. Ideal for JS/CSS/fonts/images.
- What it costs: a build step that hashes filenames and rewrites every reference, plus cache-busting discipline. It only works where you control the URL; you can't fingerprint the top-level HTML document, an API resource, or a user's uploaded file.
Choose conditional requests when the URL must stay constant and correctness-on-change matters; prefer immutable + fingerprinted URLs for versioned static assets you build; and remember they combine — fingerprinted assets for the shell, conditional revalidation for the HTML that points at them.
Takeaways
- A validator plus
If-None-Match/If-Modified-Sinceturns "re-download" into "confirm": a 304 saves the body, but still costs one round-trip. - ETag (content-based, byte-exact when strong) is more reliable than Last-Modified (1-second, clock-dependent); when both are sent, the ETag wins.
- Cache-Control and validators compose —
max-ageeliminates requests while fresh; revalidation keeps you correct once stale;no-cacheforces revalidation every time. - For versioned build artifacts, immutable + fingerprinted URLs beat conditional requests by removing the revalidation round-trip entirely; use conditional requests where the URL can't change.
Sources: MDN Web Docs — "HTTP conditional requests," ETag, If-None-Match, Last-Modified, and "HTTP caching"; RFC 9110 (HTTP Semantics) §8.8 (validator fields) and §13 (conditional requests); Google web.dev, "Prevent unnecessary network requests with the HTTP Cache"; Apache httpd FileETag documentation; DesignGurus, "Caching in System Design." Re-authored/Deepened for this guide.
🤖 Don't fully get this? Learn it with Claude
Stuck on What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load** (System Design) and want to truly understand it. Explain What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **What are HTTP Conditional Requests ETag, If‑None‑Match, Last‑Modified and How They Reduce Load** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.