Design a CDN: the content delivery network system design walkthrough
The request to design a CDN sounds like a one-liner — put the files closer to the users — and that one line hides the whole interview. A content delivery network system design is not a file server with extra machines; it is a globally distributed read-through cache where a request from a phone in Mumbai should be answered by a server a few milliseconds away instead of crossing an ocean to your origin, and where the hard parts are the two problems nobody mentions in the one-liner: how a client is routed to the nearest healthy edge, and how a cached object stays correct — fresh, and invalidated the instant it changes — without a flood of misses stampeding the one origin behind it. Latency is physics: bytes crossing half the planet cost real milliseconds. The entire point of a CDN is to shorten that distance for the overwhelming majority of requests, so the caching problem and the geography problem are the same problem.
That reframe is the spine of the round. "Serve content faster" becomes answer as close to the user as possible, on a cache hit, and make the rare miss both cheap and safe for the origin. Everything after it follows: you spread edge servers across points of presence worldwide, you route each user to the best one, you decide how those edges fill with content and how long they trust it, and you make sure that when a video suddenly goes viral — or when the origin falls over — the edges absorb the storm instead of forwarding it. Get that framing out loud early and the rest of the system design interview is a series of defensible decisions rather than a scramble. This walkthrough follows the path a strong candidate draws on the whiteboard — requirements, estimate, API and data model, high-level design, then the deep dives — naming the follow-up the interviewer uses at each step to tell a memorised answer from real understanding.
How the CDN round is actually run
A CDN round runs about 45 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is a favourite because the one-line problem opens onto deep water fast: the moment you draw "edge servers near the users," the question becomes how does a given user reach the right edge and what happens on a cache miss, and every answer after that compounds from those two. The clock is a touch tighter than the hour-long rounds, so you have to spend your minutes on routing, freshness and origin protection, not on TLS handshakes or a billing model.
The interviewer rarely lets "edge servers cache the content" stand. "A video suddenly goes viral in one region — a million requests a minute for a file that isn't in that region's cache yet. What happens at the edge, and what happens to your origin?" That question — a cache-miss storm against a single origin — is the one a copied answer can never rehearse, and the one this round is really testing.
The rounds here are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a tighter clock, and a follow-up after every decision you make.
The five-step framework for any HLD round
Every high-level design answer fits the same spine. Say it out loud at the start so the interviewer knows you have a plan, then walk it:
- Clarify requirements — functional and non-functional, and what you are explicitly not building.
- Estimate — the read:write ratio, the target cache-hit ratio, and request volume per PoP, to justify every later choice.
- High-level design — the boxes and arrows: the client, the routing layer, the edge PoPs, the regional/shield tier, and the origin.
- Deep dive — the two or three decisions that actually decide this system.
- Follow-ups — bottlenecks, failure modes, and what you would change at ten times the scale.
What the debrief measures
The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: problem solving (did you reframe it as a distributed read-through cache and name routing and freshness as the two hard problems), communication (did you state the GeoDNS-versus-anycast trade-off and why versioned URLs beat a purge, each in a single sentence), technical execution (a PoP/edge topology with an origin-shield tier, TTL-based freshness, consistent hashing and LRU at the edge, request coalescing and serve-stale under stress), and time management (did you spend your 45 minutes on routing, freshness and origin offload, not on peripheral features). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.
Step 1 — Requirements: what a CDN must do
Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.
| Type | Requirement |
|---|---|
| Functional | Serve cached content (static assets and media) to users from a nearby location; fetch from the origin on a cache miss and cache it; honour per-object freshness (TTL / Cache-Control); support invalidation — explicit purge and versioned URLs; route each client to the nearest healthy PoP; terminate TLS at the edge. |
| Functional (optional) | Dynamic content acceleration, signed/expiring URLs, image resizing at the edge, edge compute, per-customer analytics — call these out, then scope most of them for later. |
| Non-functional | Low latency for users anywhere; very high read throughput; a high cache-hit ratio so most requests never leave the edge; protect and offload the origin (it should barely feel global traffic); high availability across PoPs (one PoP dying must not break the service); freshness is eventually consistent — a changed object propagates quickly, not atomically. |
The sentence that earns points: "This is a globally distributed read-through cache — the win is answering near the user on a cache hit, and the two hard problems are routing a user to the nearest healthy edge and keeping cached objects fresh and invalidated without a miss storm hitting the single origin." Say that and you have framed the entire design before drawing a box.
Step 2 — Capacity estimation (back of the envelope)
Estimation here is about proving one thing: the cost is the miss, not the hit — hits are cheap and local, and the whole design exists to keep the miss rate low and the miss cheap.
| Quantity | Assumption | Result |
|---|---|---|
| Read:write ratio | overwhelmingly reads — content is published rarely, read constantly | optimise the read path ruthlessly; writes (publishes/purges) are a slow control-plane concern |
| Cache-hit ratio | target 90%+ at the edge for cacheable content | only <10% of requests become misses — so the origin is sized for the miss stream, not total traffic |
| Requests per PoP | high, bursty, concentrated on a small set of hot objects | a Zipf-like popularity curve — a few objects are most of the traffic, so replicate hot objects and let the long tail be re-pulled |
| Edge storage | finite per edge; far smaller than the full catalogue | you cannot cache everything — you need an eviction policy (LRU) and the long tail lives at the origin |
Two conclusions fall out, and you should say both out loud. First, hits are free and misses are expensive, so the architecture is one long effort to raise the hit ratio and to make each miss resolve with a single origin fetch — which points straight at a cache hierarchy. Second, popularity is skewed: a handful of hot objects dominate, so hot-object replication and herd control matter far more than uniform placement. You have now pointed the interview at the real deep dives — routing, freshness/invalidation, and origin offload — before anyone asked.
Step 3 — API and data model
The API is deliberately small: clients read objects, publishers write them to the origin, and the control plane invalidates.
| Operation | Shape |
|---|---|
GET /{object} |
the hot path — served from the nearest edge; conditional reads use If-None-Match: <etag> so a revalidation can return 304 Not Modified. |
PUT /{object} (publish) |
the publisher writes a new object (or version) to the origin; edges pick it up lazily on the next miss (pull) or via a push. |
PURGE /{path} |
control-plane invalidation — drop an object (or prefix) from every edge immediately. |
| versioned URL |
…/app.<content-hash>.js — a new version is a new URL, so no invalidation is needed at all. |
The data model is thin. The origin is the source of truth; the edge cache is derived and disposable:
| Entity | Notes |
|---|---|
object |
key, bytes, content_hash, Cache-Control/ttl, ETag, last_modified — the asset and its freshness metadata. |
edge_cache_entry |
(object_key, bytes, expires_at, last_access) — a cached copy at one edge; last_access drives LRU eviction; ephemeral. |
routing_map |
PoP locations, health and load — used to pick the edge for a client. |
origin_mapping |
which origin backs which hostname/path, plus the shield tier in front of it. |
Step 4 — High-level design: edges, the cache hierarchy and the origin
Now the boxes. A request is answered as close to the user as possible, and only the rare miss climbs the hierarchy toward a single origin:
- Routing layer: decides which PoP serves a given client, using GeoDNS and/or anycast, and must be health- and load-aware so a dead or overloaded PoP is taken out of rotation.
- Edge server (in a PoP): holds cached objects and answers on a cache hit. On a miss it fetches from the next tier up, caches the object (respecting TTL), and serves it. Evicts with LRU under finite storage.
- Regional edge cache / origin shield: a mid-tier in front of the origin. Many edge misses for the same object collapse into one fetch here, so the origin sees a tiny, smooth fraction of global traffic.
- Origin: the source of truth — your storage or application. Sized for the miss stream only, protected by the shield.
- Control plane: config, TTLs, and purge commands; propagates invalidations out to the edges.
- Feedback: PoP health and load signals flow back into the routing layer so routing follows reality, not a static map.
Say explicitly that this is a tiered read-through cache: the edge answers hits locally, a miss climbs edge → shield → origin and the object is cached on the way back, and the origin is deliberately shielded so a global audience feels like a trickle. Naming that tiering — and the fact that hits never leave the edge — is a signal the interviewer is listening for.
Deep dive: request routing — finding the nearest edge (GeoDNS vs anycast)
This is the first decision the round is really about. The follow-up is the geography: how does a user in Mumbai actually end up talking to a Mumbai edge, and what happens the moment that PoP dies?
There are two well-known mechanisms, and the strong answer names both, picks a combination, and justifies it:
| GeoDNS (DNS-based routing) | Anycast (BGP routing) | |
|---|---|---|
| Idea | the CDN's authoritative DNS resolves the hostname to the IP of a nearby edge, chosen from the resolver's location, health and load | every PoP announces the same IP; the internet's BGP routing delivers the packet to the topologically nearest PoP |
| Load awareness | high — DNS can steer away from a hot PoP and weight responses | coarse — routing follows network topology, not your load |
| Failover speed | as slow as the DNS TTL (cached resolvers keep sending users to a dead edge until it expires) | near-instant — withdraw the route and traffic reroutes automatically |
| Precision | limited to the resolver, not the user (public resolvers can mislocate) | precise at the network level; but routes can flap and move sessions |
| Best for | load-aware steering, weighted rollouts | fast failover, DDoS absorption, a single stable IP |
In practice you combine them: anycast to a region for a stable IP and instant failover, GeoDNS (or application-level steering) for finer, load-aware placement — and both must be health-aware, pulled from a live view of which PoPs are up and how loaded they are. Then say the one sentence that shows range: GeoDNS gives me precise, load-aware control but failover is bounded by the DNS TTL, while anycast gives near-instant failover and a single IP at the cost of coarse load control — so I'd lean anycast for resilience and layer DNS steering on top for load. That is the trade-off the round is testing.
"A user in Mumbai requests a file — how do they end up on the Mumbai edge, and what happens when that PoP goes down?" With GeoDNS, the CDN's authoritative DNS sees the request's rough location and returns the IP of a healthy nearby edge; with anycast, every PoP announces the same IP and BGP delivers the packet to the nearest one. When the PoP dies, anycast reroutes almost instantly because the route is withdrawn, while pure DNS failover waits for the TTL to expire — which is why real CDNs lean on anycast for failover and keep routing health- and load-aware.
System Design
HLD · Infrastructure · 45 min
Design a CDN out loud, with follow-ups
Deep dive: cache population, freshness and invalidation
Routing decides where a request lands; this decides what the edge holds and whether you can trust it. The follow-up is the update: you publish a new version of a file — how fast does every edge start serving it, and how do you make that happen?
Start with how an edge even fills. There are two models, and you should name both:
- Pull (origin-pull / lazy): the edge caches an object on the first miss in that region. Simple, self-healing, and it never wastes storage on objects nobody wants — the first user in a region pays the miss, everyone after gets the hit. This is the default.
- Push: the origin proactively pushes content to edges ahead of demand — useful for a known launch or a large static library where you don't want the first user anywhere to eat a cold miss. The cost is pushing objects that may never be requested.
Then freshness — how long an edge trusts what it holds:
-
TTL /
Cache-Control: max-agesets how long an object is fresh; while fresh, it is served with zero origin contact. - On expiry, the edge revalidates cheaply:
If-None-Match: <etag>(orIf-Modified-Since), and the origin replies304 Not Modifiedif nothing changed — a tiny response instead of re-downloading the object. - stale-while-revalidate serves the slightly stale copy instantly and refreshes it in the background, so users never wait on a revalidation.
Finally invalidation — forcing a change before TTL expiry — where the strong candidate shows judgement:
- Explicit purge: a control-plane command drops an object (or prefix) from every edge. It works, but it is a global operation with a brief race until all edges have cleared, and it is easy to over-purge.
-
Versioned / fingerprinted URLs: put a content hash in the path (
app.9f2c1a.js). A new version is a new URL that cannot collide with the cached old one, so the old object can be cached effectively forever and the switch is instant — no purge required. This is the cleanest cache-busting and the answer that scores.
Tie it together with the cache hierarchy: because misses collapse at the origin shield, even a cold object pulled by thousands of edges resolves to a handful of origin fetches, not thousands. Say it plainly: static assets get long TTLs and versioned URLs so updates are instant and the origin is barely touched; truly dynamic objects get short TTLs or explicit purges.
"You push a new version of a file — how fast does every edge serve it?" If I version the URL with a content hash, instantly: the new version is a new URL, so there's nothing to invalidate and the old one can stay cached. If I must keep the same URL, I issue a purge from the control plane to drop it from every edge, accepting a brief propagation race, or I rely on a short TTL so it refreshes on the next revalidation. For static assets I default to versioned URLs with long TTLs; that keeps updates instant and the origin cold.
System Design
HLD · Infrastructure · 45 min
Defend your freshness and invalidation model out loud
Deep dive: scale, hot content and origin offload
Routing and freshness keep a steady state healthy; this decides what happens under the two stresses that break a naive CDN — a viral object and a dead origin. The follow-up is the storm: a file goes viral in one region and it isn't cached there yet — what stops a million simultaneous misses from flattening your origin?
The answer is a stack of defences, each worth naming:
- Object placement with consistent hashing: within a PoP, objects are spread across the edge servers by consistent hashing, so adding or removing a server moves a minimal set of keys rather than reshuffling everything — and so every edge agrees on which server owns a key.
- Replicate hot objects: a Zipf-hot object is deliberately replicated to more servers so a single viral file doesn't melt one box; popularity, not a uniform hash, decides how many copies.
- Request coalescing (the key defence): when many clients miss on the same object at once, the edge issues one origin fetch and makes everyone else wait on that single in-flight request. A million concurrent misses become one upstream fetch — this is what actually kills the thundering herd.
- Origin shield: coalescing repeats at the shield tier, so even misses from many PoPs for the same object collapse to one origin fetch. The origin sees a trickle.
- Serve stale on failure: if the origin is slow or down, edges serve the stale copy past its TTL rather than returning an error — availability beats strict freshness for cached content. The shield keeps retrying in the background.
Say the point plainly: the origin must never feel the crowd. Consistent hashing places objects, replication spreads the hot ones, coalescing and the shield fold a stampede into a single fetch, and serve-stale means an origin outage degrades to slightly-old content instead of an error page. That is how a CDN turns a viral spike or an origin failure into a non-event.
"The origin goes down — what do the edges serve?" They keep serving: on a miss or an expiry they serve the stale cached copy past its TTL rather than erroring, while the origin shield retries the origin in the background. Fresh objects are unaffected because they never touch the origin anyway. The design choice is explicit — for cached content, availability (serve slightly stale) beats strict freshness — so an origin outage degrades gracefully instead of becoming a global error.
The follow-ups that decide the CDN round
Scan the probes from each deep dive and they rhyme. Master these five and you are ready for a version of the question that is not drawn exactly like this one.
- "How does a user reach the nearest edge, and what happens when a PoP dies?" → GeoDNS returns a nearby healthy edge IP (load-aware, but failover waits for the DNS TTL); anycast announces one IP from every PoP and BGP routes to the nearest (near-instant failover, coarser load control). Combine them and keep routing health-aware. (technical execution)
- "Pull or push — how do edges get their content?" → Pull by default: cache on the first miss, self-healing, no wasted storage; push proactively only for known launches or large libraries where a cold first miss is unacceptable. (problem solving)
- "You change a file — how do you invalidate it everywhere?" → Best: versioned/fingerprinted URLs, so a new version is a new URL and nothing needs purging; otherwise an explicit control-plane purge (global, with a brief race) or a short TTL. Default to versioned URLs with long TTLs. (communication)
- "A file goes viral and isn't cached — what stops the origin from melting?" → Request coalescing (one in-flight origin fetch per key, everyone waits on it) plus the origin shield (misses from many PoPs collapse to one), with hot-object replication across edge servers. (technical execution)
- "The origin is down — what do users see?" → Fresh objects are served normally; for misses/expiries the edges serve stale past TTL while the shield retries — availability over strict freshness, so an outage degrades to slightly-old content, not errors. (problem solving)
Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.
How to prepare: a one-week plan
You do not need fifty systems. You need this one cold and the framework automatic, so the next "design X" is just new nouns in the same five steps.
- Days 1–2: Learn the five-step framework until it is reflex. Do the CDN end to end on paper — requirements, estimate, API, data model, and the routing/edge/shield/origin boxes.
- Days 3–4: Drill routing and freshness: GeoDNS versus anycast and when you'd combine them; TTL, revalidation and stale-while-revalidate; purge versus versioned URLs. Say each trade-off in one sentence.
- Days 5–6: Own origin offload — consistent hashing for placement, hot-object replication, request coalescing, the origin shield, and serve-stale on failure. Answer each of the five follow-ups out loud.
- Day 7: A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.
This post is the deep walkthrough behind one row of the broader Top 25 system design interview questions — once the CDN is automatic, the same spine carries the rest of the list.
Frequently asked questions
How do I design a CDN step by step?
Follow the five-step HLD framework. Clarify requirements (serve cached static and media content from near the user, fetch from origin on a miss and cache it, honour TTL/Cache-Control, support purge and versioned-URL invalidation, route to the nearest healthy PoP; non-functionally: low global latency, very high read throughput, a 90%+ cache-hit ratio, origin protection, and HA across PoPs). Estimate to show the cost is the miss, not the hit, and that popularity is skewed toward a few hot objects. Define the small API (GET with conditional revalidation, publish to origin, PURGE, versioned URLs). Draw the design (client → routing layer → nearest edge PoP → regional edge cache / origin shield → origin, plus a control plane and health/load feedback). Then deep-dive request routing (GeoDNS vs anycast), cache population/freshness/invalidation, and origin offload (consistent hashing, coalescing, shield, serve-stale). Finish on the follow-ups about a viral spike and an origin outage.
How does a CDN route a user to the nearest edge?
Two mechanisms, usually combined. GeoDNS: the CDN's authoritative DNS resolves the hostname to the IP of a nearby edge, chosen from the resolver's approximate location plus each PoP's health and load — precise and load-aware, but failover is only as fast as the DNS TTL because resolvers cache the answer. Anycast: every PoP announces the same IP address, and the internet's BGP routing naturally delivers each packet to the topologically nearest PoP — failover is near-instant because withdrawing a route reroutes traffic automatically, but load control is coarse. Real CDNs use anycast for a stable IP and fast failover and layer DNS or application-level steering on top for finer, load-aware placement, with all of it driven by live health signals.
What's the difference between a pull and a push CDN?
A pull CDN caches lazily: the edge fetches an object from the origin on the first cache miss in that region, then serves it from cache until it expires. It is simple, self-healing, and never wastes storage on unrequested objects — the first user in a region pays the miss and everyone after gets a hit. A push CDN has the origin proactively upload content to edges ahead of demand, which avoids a cold first miss for a known launch or a large static library but spends storage and bandwidth on objects that may never be requested. Most CDNs are pull by default and use push selectively for predictable, high-value content.
How does cache invalidation work in a CDN?
Three ways, in order of preference. Versioned (fingerprinted) URLs are cleanest: put a content hash in the path so a new version is a brand-new URL that cannot collide with the cached old one — nothing needs invalidating and the old object can be cached nearly forever. An explicit purge is a control-plane command that drops an object or prefix from every edge immediately; it works but is a global operation with a brief propagation race. A short TTL lets objects refresh on their own via revalidation (ETag/If-None-Match returning 304 Not Modified), optionally with stale-while-revalidate so users never wait. The usual pattern is long TTLs plus versioned URLs for static assets, and short TTLs or purges for genuinely dynamic content.
How does a CDN handle a viral spike without overloading the origin?
With layered origin protection. Within a PoP, objects are placed across edge servers by consistent hashing and hot objects are replicated to more servers so one viral file doesn't overwhelm a single box. The key defence is request coalescing: when thousands of clients miss on the same object at once, the edge issues a single origin fetch and makes everyone wait on that one in-flight request, so a million concurrent misses become one upstream call. The origin shield tier repeats this across PoPs, collapsing misses from many regions into one origin fetch. And if the origin is slow or down, edges serve the stale cached copy past its TTL rather than erroring, so the origin only ever sees a small, smooth trickle of traffic.
How do I practise a system design interview out loud?
Reading a walkthrough is not the same as defending a design while someone interrupts. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a shared design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can read what a cache hit is. The round is won on protecting the origin out loud.
Pick the CDN / Distributed Database round, draw the routing, edge-hierarchy and origin design on the canvas, and defend it out loud to a voice interviewer that keeps asking "a video goes viral in one region — what happens to your origin?" and "you push a new version — how fast does every edge serve it?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).




Top comments (0)