When would you invalidate cached API content by changing the URL — putting a version or content hash in the path — rather than relying on the origin being revalidated? What do you give up with each approach?
answer
- Change the bytes → change the URL, or don't
- Immutable URL = no invalidation to forget
- Discovery becomes the fresh thing
- Stable URL = linkable, but round trips + purge work
- Pointer + immutable payload hybrid
basics
~20 sChange the URL when the representation is immutable and the client learns the new URL from somewhere else — then cache forever with no invalidation at all. Rely on revalidation when the identity must stay stable, at the cost of a round trip per check and an origin that must stay reachable.
solid answer
~50 sThe two strategies answer different questions. **Versioned or content-hashed URLs** make each version a distinct resource: `/exports/a3f9c1e.json`. Because the bytes at that URL never change, you can serve `max-age=31536000, immutable` and never invalidate anything. There is no purge to forget and no staleness window. The precondition is that clients must **discover** the new URL — from a manifest, an index document, a link in a parent resource — so the pointer, not the content, becomes the thing you keep fresh. **Validator-based revalidation** keeps a stable URL and lets the cache re-check with the origin when the TTL lapses. That is the right choice when the URL is the API's public contract — `/v1/orders/771` — where clients bookmark, link and hold references. The costs are a round trip on every revalidation and dependence on origin availability. The hybrid used in practice: stable URLs for entities, immutable URLs for bulky derived artifacts, and a short-TTL pointer resource linking to the immutable one.
code
http · 11 linesGET /v1/catalog/current HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=30
{"current":"/v1/catalog/2026-08-12-a3f9c1e.json"}
GET /v1/catalog/2026-08-12-a3f9c1e.json HTTP/1.1
HTTP/1.1 200 OK
Cache-Control: public, max-age=31536000, immutablego deeper
Know that if the URL changes whenever the content changes, caches can hold it forever, whereas a stable URL needs a TTL or a re-check.
Compare the two explicitly — immutability versus discovery, round trips versus zero checks — and know the pointer-plus-immutable-payload hybrid.
Argue selection criteria: payload size, whether identity is part of the contract, convergence requirements, and the reliability value of making missed invalidation impossible.
Treat it as an API contract decision with operational consequences — which resources are content-addressed, how discovery stays fresh, storage and rollback implications — and keep it firmly separate from API version negotiation.
## Two different ideas of identity Every caching strategy ultimately answers: **when the bytes change, does the URL change too?** If yes, cache entries never become wrong. Each URL maps to exactly one immutable representation, so caches can hold it forever and invalidation ceases to exist as a problem. If no, then the same URL must sometimes yield different bytes, and something has to detect that — expiry, revalidation, or an explicit purge. ## Versioned and content-addressed URLs Embedding a version number, build id, revision or content hash in the path creates a new resource per version. The strategy's properties: **What you gain.** Cache lifetimes of a year with the `immutable` directive, meaning caches need not even revalidate on a user-initiated reload. Zero invalidation machinery, so no purge to fail and no missed related resource. Trivially safe rollback, because the previous version's URL still exists and still serves. Perfect cache keys with no variant confusion. **What you give up.** Discovery becomes your problem: something must tell the client the new URL, and that pointer is now the freshness-critical object. Clients holding an old URL keep getting old content indefinitely, silently — sometimes desirable (a pinned export) and sometimes a bug (a client that never learns of the new version). You accumulate versions in storage. And the URL is no longer a stable human-meaningful identifier, which is awkward for anything users bookmark or share. **Where it fits an API.** Bulk exports and report artifacts. Generated files and schema documents. Any large, derived payload where regenerating is expensive and the exact version matters. Also configuration snapshots, where pinning a client to a known revision is a feature rather than a hazard. ## Stable URLs with revalidation Here `/v1/orders/771` always means "the current state of order 771". After the freshness lifetime lapses, a cache asks the origin whether its copy is still good, and the origin answers either "unchanged" — cheaply, without resending the body — or with the new representation. **What you gain.** A durable, meaningful identity that clients can link, bookmark and reference. Clients automatically converge on current state without needing to discover anything. It matches how consumers expect a REST resource to behave. **What you give up.** A round trip whenever a cache checks. That saves bandwidth but not latency, which matters for a mobile client on a slow link and for any cache far from the origin. You also depend on origin availability at revalidation time unless you have arranged for stale content to be served during errors. And you now genuinely have an invalidation problem: TTLs to tune and, if you want fast write visibility, purges to issue. ## The pointer pattern: use both The strongest arrangement combines them. Keep one small, stable, short-TTL resource whose entire job is to name the current immutable URL, and make the heavy payload immutable and cached forever: - `GET /v1/catalog/current` → short TTL, returns `{"url": "/v1/catalog/2026-08-12-a3f9c1e.json"}` - `GET /v1/catalog/2026-08-12-a3f9c1e.json` → `max-age=31536000, immutable` Only a few hundred bytes are ever revalidated. The megabytes are fetched once per version, per cache, forever. Rollback is changing the pointer. Clients that want stability can pin the immutable URL deliberately. ## Choosing between them Ask four questions. 1. **Is the identity part of the public contract?** If consumers link to or store the URL, keep it stable. 2. **Is the payload large or expensive to produce?** The bigger it is, the more an immutable URL pays off. 3. **Must every client converge on the latest state automatically?** If yes, a stable URL does that natively; versioned URLs need a discovery step you must build and keep fresh. 4. **How costly is a missed invalidation?** Immutable URLs make that failure mode impossible, which is a genuine reliability argument, not just a performance one. ## A caution about API versioning Do not conflate this with `/v1` versus `/v2` in your API path. That is a contract-shape decision about breaking changes and has nothing to do with cache invalidation. Putting a build number in your API's base path so you can "invalidate the cache on deploy" invalidates every client's cached content on every deploy, breaks stored links, and is a blunt instrument where a purge or a TTL would do. Content-addressed URLs are for individual representations whose bytes are genuinely fixed, not for the API surface as a whole.
- If content-addressed URLs remove the invalidation problem entirely, why not use them everywhere in an API?Because they move the problem to discovery rather than removing it. Clients must be told the new URL by something, and that pointer is now the freshness-critical resource. They also break the expectation that a resource URL denotes current state, so bookmarks and stored references silently pin old data, and you accumulate versions in storage. They fit large derived artifacts, not the entity URLs that make up the API's public contract.
- What is the main cost of relying on revalidation against a stable URL?Latency and origin coupling. Revalidation saves bandwidth when nothing changed, but the client still pays a round trip to find that out, which hurts most on slow links and for caches far from the origin. It also means the origin must be reachable at revalidation time unless you have configured caches to serve stale content during errors. An immutable URL pays neither cost because there is nothing to check.
Immutable URLs are dated print editions on a shelf; a stable URL is a noticeboard you have to walk over and re-read to see whether anything changed.
saying these in an interview costs you the question
- Putting a build number in the API base path and calling it cache invalidation.
- Believing versioned URLs eliminate the freshness problem rather than relocating it to the pointer.
- Assuming revalidation is free because the body is not resent, ignoring the round-trip latency.
- Using immutable URLs for entity resources that clients are expected to bookmark and follow.
- Treating API contract versioning and content-addressed URLs as the same decision.