How CDN cache invalidation actually works
How CDN cache invalidation uses TTL expiry, purge APIs, surrogate keys, stale-while-revalidate, and versioned URLs to keep edge caches consistent with origin servers.
The Problem Statement
Interviewer: "You just published an article on your company's blog. The content has a typo in the headline. You fix it in your CMS and hit publish. But users around the world are still seeing the old headline. Why? And how do you make the CDN serve the updated content as fast as possible?"
This question tests three things: your understanding of cache hierarchies and how CDNs propagate content, your knowledge of the specific invalidation mechanisms available (TTL, purge, surrogate keys, versioned URLs), and whether you can reason about the tradeoffs between freshness and performance.
Most candidates say "just purge the cache." That is correct but shallow. A strong candidate explains why purge propagation takes time, why TTL-based expiry exists as a safety net, how surrogate keys enable surgical invalidation of related content, and why versioned URLs sidestep the invalidation problem entirely.
Clarifying the Scenario
You: "Good question. Let me make sure I understand the setup."
You: "When you say 'CDN,' are we talking about a specific provider like CloudFront, Fastly, or Cloudflare, or should I keep it general?"
Interviewer: "Keep it general, but feel free to reference specific providers where their approaches differ."
You: "Got it. And is this about a single page that needs updating, or are we talking about invalidating a batch of related content, like updating a product price that appears on 500 different pages?"
Interviewer: "Cover both. Start with the single-page case, then scale up."
You: "One more thing. Should I assume the CDN has a single layer of edge caches, or a multi-tier setup with shield/origin-shield nodes?"
Interviewer: "Multi-tier. That is the realistic setup."
You: "Perfect. I will structure my answer around four invalidation strategies: TTL-based expiry, active purge APIs, surrogate keys for tag-based invalidation, and versioned URLs. Then I will cover the multi-layer propagation challenge and cache stampede prevention."
My Approach
I break this into five layers:
- TTL-based expiry: The passive, time-based approach that every CDN uses as the baseline
- Active purge and ban APIs: How you force immediate invalidation when TTL is too slow
- Surrogate keys and tag-based invalidation: Surgical invalidation of all content related to a specific entity
- Versioned URLs and content hashing: Sidestepping invalidation entirely by changing the URL
- Multi-layer cache propagation: How invalidation flows from origin through shield to 300+ edge locations
The mental model I use: think of CDN invalidation as a distributed consistency problem. The origin server is the source of truth. The edge caches are replicas. When the source of truth changes, you need to propagate that change to all replicas. The challenge is doing this fast enough for user expectations while not destroying the performance benefits of caching.
Phil Karlton famously said, "There are only two hard things in Computer Science: cache invalidation and naming things." CDN cache invalidation is the distributed systems version of this problem. You are invalidating caches across 300+ locations on 6 continents, each serving different subsets of traffic.
The Architecture
Here is how a multi-tier CDN handles content serving and invalidation. The key insight is that invalidation is not one operation. It is a cascade that must traverse every layer of the cache hierarchy.
Let me walk through the flow.
When a user requests content, their DNS resolves to the nearest edge PoP (Point of Presence). The edge checks its local cache. On a cache hit, the response goes back in 5-20ms. On a miss, the edge forwards the request to a shield node. The shield is a second cache layer that aggregates requests from multiple edge nodes in the same region. This reduces origin load by ~90%.
If the shield also misses, it fetches from the origin server. The response flows back through the shield (which caches it) to the edge (which also caches it) to the user.
When content changes, the origin needs to invalidate every copy across this entire chain. That is where the four invalidation strategies come in.
TTL: The Passive Baseline
TTL (Time To Live) is the simplest invalidation strategy: set an expiry time on cached content and let it expire naturally. When the TTL for a cached object passes, the next request for that object triggers a revalidation with the origin.
The origin controls TTL via HTTP headers:
Cache-Control: public, max-age=3600, s-maxage=86400
max-age=3600 tells the browser cache to hold the content for 1 hour. s-maxage=86400 tells the CDN (shared cache) to hold it for 24 hours. The s-maxage directive overrides max-age for shared caches, letting you set different TTLs for browser and CDN.
The tradeoff is straightforward: short TTLs mean fresher content but more origin traffic. Long TTLs mean better performance but stale content after updates.
Notice the 304 Not Modified response. When the TTL expires and the edge revalidates, the origin can respond with 304 if the content has not actually changed. This saves bandwidth because the full response body is not retransmitted. The edge uses the ETag or Last-Modified header from the original response to make this conditional request.
The problem with TTL-only: if you fix that typo in the headline, you have to wait up to s-maxage seconds before all edge caches expire and fetch the updated content. With a 24-hour TTL, some users will see the typo for up to a full day.
Active Purge: When TTL Is Too Slow
Active purge is the "pull the fire alarm" button. Instead of waiting for caches to expire, you tell the CDN to delete specific cached objects immediately. Every major CDN provider offers a purge API, but they work differently.
Fastly: Instant purge. Completes in ~150ms globally. Uses their VCL (Varnish Configuration Language) engine to immediately mark objects as stale. When the next request arrives, the edge fetches fresh content from the origin. Fastly's instant purge is the gold standard.
Cloudflare: Purge completes in under 30 seconds for most PoPs. Supports purge by URL, purge by tag (via Cache-Tag header), and purge everything. Their "purge everything" is fast but aggressive. It invalidates the entire zone, not just one URL.
CloudFront: Invalidation requests are queued and processed asynchronously. Propagation takes 5-15 minutes to complete across all edge locations. There is no "instant purge" option. CloudFront gives you 1,000 free invalidation paths per month; additional ones cost $0.005 each.
A common interview mistake: saying "just purge the cache" without acknowledging propagation delay. On CloudFront, a purge takes up to 15 minutes. During that window, users are still seeing stale content. You need a strategy for this gap.
Here is how the CDN provider comparison looks in practice:
| Provider | Purge speed | Purge by tag | Wildcard purge | Cost |
|---|---|---|---|---|
| Fastly | ~150ms | Yes (Surrogate-Key) | Yes | Free (unlimited) |
| Cloudflare | < 30s | Yes (Cache-Tag) | Yes | Free (included) |
| CloudFront | 5-15 min | No (use paths only) | Yes (with wildcards) | 1,000 free/month, then $0.005/path |
| Akamai | 5-7s (Fast Purge) | Yes (cache tags) | Yes | Depends on contract |
The purge API typically accepts one of three targets:
- Purge by URL: Invalidate one specific URL. Simple but tedious when one content change affects hundreds of pages.
- Purge by tag/surrogate key: Invalidate all objects tagged with a specific key. This is the surgical approach.
- Purge all: Nuclear option. Invalidate everything in the cache. Useful during deployment but causes a cache stampede.
Surrogate Keys: Surgical Invalidation at Scale
This is where the architecture gets interesting. Imagine you update a product's price on an e-commerce site. That price appears on the product detail page, the category listing page, the search results page, the homepage "featured products" section, and the shopping cart. That is 5+ different cached URLs that all need invalidation.
Purging by URL means you need to know every URL that contains this product's price. That is fragile and error-prone. Surrogate keys solve this by tagging cached content with metadata that identifies what entities it contains.
When the origin generates a response, it includes a header listing all the entities that response depends on:
Surrogate-Key: product-12345 category-electronics homepage-featured
Now, when product 12345's price changes, you send one purge command:
POST /purge/product-12345
The CDN finds every cached object tagged with product-12345 and invalidates all of them. One API call, surgical precision, zero need to enumerate URLs.
Notice that /products/67890 is not invalidated even though it is in the same cache. It does not carry the product-12345 tag, so the purge command ignores it. This is the surgical precision that surrogate keys provide.
The tradeoff: your application must explicitly tag every response with the right surrogate keys. This requires your CMS or rendering layer to track which entities contribute to each page. For a simple blog, this is easy (each article page has one tag). For a complex e-commerce site where a single page depends on products, inventory, pricing, promotions, and recommendations, the tagging logic can be complex.
Versioned URLs: Sidestep Invalidation Entirely
There is one strategy that eliminates the invalidation problem altogether for static assets: put a version identifier in the URL itself.
Instead of serving CSS at /styles/main.css, serve it at /styles/main.abc123.css where abc123 is a hash of the file contents. When the CSS changes, the hash changes, the URL changes, and the browser requests the new URL. The old URL stays cached (nobody requests it anymore), and the new URL has a cold cache that gets populated on first request.
This is what every modern build tool does. Webpack, Vite, and esbuild all produce content-hashed filenames by default.
The Cache-Control header for versioned assets is maximally aggressive:
Cache-Control: public, max-age=31536000, immutable
One year TTL, marked immutable. The CDN and browser will never revalidate this URL because the content at this URL will never change. If the content changes, the URL changes.
The limitation: this only works for content you reference by URL (scripts, stylesheets, images, fonts). It does not work for HTML pages because the URL is determined by the user typing it or following a link. You cannot change /blog/my-article to /blog/my-article-v2 every time you fix a typo. For HTML, you still need TTL + purge.
The key insight: use versioned URLs for all static assets (maximally long TTL, no invalidation needed) and TTL + active purge for HTML pages (shorter TTL, surgical invalidation when content changes). This combination covers 99% of real-world CDN caching needs.
The stale-while-revalidate Safety Net
There is one more header that completes the picture, and most candidates never mention it. The stale-while-revalidate directive tells the edge cache: "When this content expires, serve the stale version immediately and revalidate in the background."
Cache-Control: public, s-maxage=3600, stale-while-revalidate=60
This says: cache for 1 hour. After 1 hour, the content is stale. But for the next 60 seconds of staleness, serve the stale content to users instantly (zero latency) while asynchronously fetching the fresh version from the origin. After the background fetch completes, the cache is updated and subsequent users get the fresh version.
Without stale-while-revalidate, the first user after TTL expiry experiences the full revalidation latency (50-200ms depending on origin location). With it, that user still gets a fast response, just a slightly stale one.
This is especially valuable after a purge on a slow-propagating CDN like CloudFront. Even if the purge has not reached a particular edge PoP yet, the stale-while-revalidate window means users get served quickly while the cache updates in the background.
User A gets the stale content instantly. User B (arriving after the background revalidation completes) gets the fresh content. Nobody experiences a slow response. This is the ideal behavior for content where absolute real-time freshness is not critical but latency is.
The combination of all four strategies looks like this in practice:
| Content Type | TTL | Invalidation Strategy | stale-while-revalidate |
|---|---|---|---|
| Static assets (CSS, JS, images) | 1 year (immutable) | None needed (versioned URLs) | Not needed |
| HTML pages | 1-24 hours | Active purge + surrogate keys | 30-60 seconds |
| API responses (public) | 5-60 minutes | Active purge on data change | 10-30 seconds |
| API responses (user-specific) | Browser only (private) | Not CDN-cached | Not applicable |
| Real-time data | no-store | No caching | Not applicable |
The Tricky Parts
-
Cache stampede after a purge: When you purge a popular page, the next 10,000 concurrent requests all find an empty cache and all hit the origin simultaneously. This is a thundering herd problem that can overwhelm your origin. The solution is request coalescing (also called "request collapsing"): the CDN groups simultaneous cache misses for the same URL and sends only one request to the origin. All other requests wait for that one response, which is then cached and served to everyone. Fastly calls this "request collapsing." CloudFront does this at the shield layer.
-
Stale content during purge propagation: On CloudFront, a purge takes up to 15 minutes. During that window, some edge nodes have the old content and some have the new content. Users in Tokyo see the old headline while users in New York see the new one. There is no way to make this atomic across all 300+ PoPs. The mitigation is
stale-while-revalidate: the edge serves the stale content immediately while fetching the new version in the background. The user gets a fast response (stale), and the next user gets the fresh response. -
Multi-object consistency: If your page update involves changing both the HTML and a CSS file, the CDN might serve the new HTML with the old CSS (or vice versa) during the propagation window. Versioned URLs solve this for assets (the new HTML references the new CSS URL), but for multi-page updates (like a site-wide template change), there is no way to guarantee all pages flip atomically.
-
Purge storms during deployments: A deployment that changes templates across 50,000 pages triggers 50,000 purge commands. CDN providers rate-limit purge APIs. Fastly handles this gracefully with bulk purge support and surrogate keys. CloudFront throttles invalidation requests to 3,000 concurrent paths. You may need to batch purges or use "purge all" as a pragmatic alternative during major deploys.
-
Debugging cache behavior: When users report stale content, you need to determine whether the staleness is at the CDN edge, the shield, the browser cache, or an intermediate proxy. Each layer has different headers for inspecting cache status. Look for
X-Cache: HITorCF-Cache-Status: HITin response headers to identify which layer served the response. TheAgeheader tells you how long the response has been in the cache.
What Most People Get Wrong
| Mistake | What they say | Why it is wrong | What to say instead |
|---|---|---|---|
| Purge is instant | "Just purge and it updates everywhere" | CloudFront takes 5-15 minutes. Even Fastly's "instant" purge has a propagation window | "Purge speed varies by provider. Plan for the propagation gap with stale-while-revalidate" |
| Short TTLs everywhere | "Set max-age=60 to keep things fresh" | Destroys cache hit rate. Origin sees 300x more traffic | "Long TTLs for performance, active purge for freshness. Different content types get different TTLs" |
| Ignoring multi-tier caches | "The CDN has caches at the edge" | Modern CDNs have 2-3 cache tiers: edge, shield, and sometimes regional | "Invalidation must cascade through all tiers: edge, shield, origin" |
| No stampede prevention | "Purge the popular page" | 10,000 concurrent misses hit origin simultaneously | "Use request coalescing so only one miss reaches origin. Other requests wait for the response" |
| Versioned URLs for everything | "Hash every URL so we never invalidate" | Cannot version HTML pages because users bookmark and link to them | "Versioned URLs for static assets, TTL + purge for HTML pages" |
How I Would Communicate This in an Interview
Here is how I would actually say this:
"CDN cache invalidation is fundamentally a distributed consistency problem. You have the origin as the source of truth and 300+ edge caches as replicas. When the origin changes, you need to propagate that change.
There are four strategies, and in practice you use a combination of them.
First, TTL-based expiry. Every cached object has a time-to-live set via Cache-Control headers. When the TTL passes, the edge revalidates with the origin. This is the passive baseline. I set long TTLs (hours to days) for good cache hit rates.
Second, active purge. When content changes and I cannot wait for TTL expiry, I call the CDN's purge API to force invalidation. But purge is not instant everywhere. On Fastly it completes in about 150 milliseconds globally. On CloudFront it takes 5 to 15 minutes. I need to plan for that propagation window.
Third, surrogate keys for surgical invalidation. Instead of purging by URL, I tag each cached response with the entities it depends on. When a product price changes, I purge the tag 'product-12345' and the CDN invalidates every page containing that product. One API call, precise invalidation.
Fourth, versioned URLs for static assets. I put a content hash in the filename, like main.abc123.css. When the file changes, the hash changes, the URL changes, and the old version stays cached harmlessly. I set a one-year TTL with the immutable flag.
In practice, I combine all four: versioned URLs for assets, long TTLs on HTML pages, active purge when an editor publishes an update, and surrogate keys to handle the 'one product appears on 500 pages' problem. The stale-while-revalidate directive acts as a safety net so users never see a latency spike during cache refresh."
Interview Cheat Sheet
- Cache hierarchy: "Modern CDNs have 3 tiers: browser cache, edge PoPs (300+), and shield/mid-tier. Invalidation must cascade through all of them."
- TTL strategy: "Long TTLs (hours to days) for performance. s-maxage for CDN, max-age for browser. Different values per content type."
- Purge speed: "Fastly: ~150ms. Cloudflare: < 30s. CloudFront: 5-15 min. Plan for the gap."
- Surrogate keys: "Tag responses with entity IDs. Purge by tag instead of by URL. One API call invalidates all pages containing that entity."
- Versioned URLs: "Content hash in filename for static assets. max-age=31536000, immutable. Never needs invalidation."
- stale-while-revalidate: "Serve stale content immediately while fetching fresh content in the background. Eliminates latency spikes on cache miss."
- Cache stampede: "Request coalescing prevents thundering herd after purge. Only one origin request per URL, other clients wait."
- 304 Not Modified: "Conditional request with ETag. Origin confirms content has not changed without resending the body. Saves bandwidth."
- Debugging: "Check X-Cache, CF-Cache-Status, and Age headers. They tell you which cache layer served the response and how old it is."
- Cost: "CloudFront charges for invalidation paths beyond 1,000/month. Fastly and Cloudflare include unlimited purges."
Test Your Understanding
Quick Recap
- CDN cache invalidation is a distributed consistency problem: propagating changes from one origin to 300+ edge replicas with minimal delay.
- TTL-based expiry is the passive baseline. Set TTLs via
Cache-Controlheaders: long for static assets, shorter for dynamic content. - Active purge forces immediate invalidation. Speed varies by provider: Fastly (~150ms), Cloudflare (< 30s), CloudFront (5-15 minutes).
- Surrogate keys enable surgical invalidation by tagging cached content with entity IDs, then purging by tag instead of by URL.
- Versioned URLs (content hashing in filenames) eliminate the need for invalidation entirely for static assets.
- Request coalescing prevents cache stampedes after purge by collapsing simultaneous cache misses into a single origin request.
stale-while-revalidateserves the old content immediately while fetching fresh content in the background, eliminating latency spikes during revalidation.- In practice, combine all strategies: versioned URLs for assets, long TTLs on HTML with active purge, surrogate keys for entity-level invalidation, and stale-while-revalidate as a safety net.
Related Concepts
- HTTP caching headers (Cache-Control, ETag, Vary): The protocol-level foundation of CDN caching. Understanding these headers is prerequisite for any CDN invalidation discussion.
- Distributed consistency models: CDN cache invalidation is an eventual consistency problem. The same tradeoffs between consistency and availability apply here as in distributed databases.
- Content delivery network architecture: Understanding the edge-shield-origin hierarchy is essential for reasoning about where caches sit and how invalidation propagates.
- Rate limiting and traffic shaping: Purge storms and cache stampedes are traffic spike problems. The same principles used in API rate limiting apply to managing origin load during invalidation events.
- Event-driven architectures: Surrogate key purging is conceptually similar to event-driven systems. A content change event triggers downstream cache invalidation, much like a domain event triggers downstream service updates.