<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gowtham Potureddi</title>
    <description>The latest articles on DEV Community by Gowtham Potureddi (@gowthampotureddi).</description>
    <link>https://dev.to/gowthampotureddi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3874592%2Fb901f929-0a60-4dd2-9dac-22ce22291bdc.png</url>
      <title>DEV Community: Gowtham Potureddi</title>
      <link>https://dev.to/gowthampotureddi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gowthampotureddi"/>
    <language>en</language>
    <item>
      <title>Caching strategies for system design interviews, explained</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 17:12:07 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/caching-strategies-for-system-design-interviews-explained-5aog</link>
      <guid>https://dev.to/gowthampotureddi/caching-strategies-for-system-design-interviews-explained-5aog</guid>
      <description>&lt;h2&gt;
  
  
  Caching strategies for system design interviews, explained
&lt;/h2&gt;

&lt;p&gt;Almost every &lt;strong&gt;system design interview&lt;/strong&gt; reaches a moment where the database is the bottleneck and someone says "add a cache" — and that is where the real question starts. &lt;strong&gt;Caching strategies&lt;/strong&gt; are not one idea; they are a set of deliberate choices about &lt;em&gt;where&lt;/em&gt; a copy of the data lives, &lt;em&gt;who&lt;/em&gt; loads it, &lt;em&gt;when&lt;/em&gt; it is written, and &lt;em&gt;what happens when it goes stale&lt;/em&gt;, and an interviewer can tell in thirty seconds whether you have a vocabulary for those choices or whether "add a cache" is where your thinking stops. A cache trades a little memory and a lot of correctness-anxiety for latency and &lt;strong&gt;database&lt;/strong&gt; offload, and the whole skill is knowing exactly which trade you are making.&lt;/p&gt;

&lt;p&gt;The reason this comes up in almost every round is that caching sits on the two axes every design cares about: &lt;strong&gt;latency&lt;/strong&gt; and &lt;strong&gt;load&lt;/strong&gt;. A read served from memory is orders of magnitude faster than one that touches disk, and every read the cache absorbs is one the database never sees. But a cache is a second copy of the truth, and the instant you have two copies you own a consistency problem — the data can be stale, two writers can race, and a key everyone wants can expire at the worst possible moment. This guide walks the choices a strong candidate names out loud: the &lt;strong&gt;read strategies&lt;/strong&gt; (&lt;strong&gt;cache-aside&lt;/strong&gt; and &lt;strong&gt;read-through&lt;/strong&gt;), the &lt;strong&gt;write strategies&lt;/strong&gt; (&lt;strong&gt;write-through&lt;/strong&gt;, &lt;strong&gt;write-back&lt;/strong&gt; and &lt;strong&gt;write-around&lt;/strong&gt;), the eviction policy that decides what to throw away, and the three failures — invalidation, the &lt;strong&gt;thundering-herd&lt;/strong&gt; stampede, and hot keys — that the follow-up questions are really about.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkql1jouw11fifuqnviey.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkql1jouw11fifuqnviey.jpeg" alt="Title card for a system design interview explainer on caching strategies, showing an application sitting between a fast in-memory cache and a slower database, with read paths labelled cache-aside and read-through and write paths labelled write-through, write-back and write-around branching out from it, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why caching strategies come up in system design interviews
&lt;/h2&gt;

&lt;p&gt;Caching rarely gets its own whiteboard prompt. It shows up &lt;em&gt;inside&lt;/em&gt; another design — a news feed, a product catalogue, a rate limiter, a URL shortener — at the moment you realise the read volume will crush the database. You draw a box labelled "cache," and the interviewer leans in: not &lt;em&gt;whether&lt;/em&gt; to cache, but &lt;em&gt;how&lt;/em&gt;. Which strategy, what you cache, how you keep it honest, and what happens the moment it fails. Caching is a favourite probe precisely because "add a cache" is the easy half of the sentence and everything true and hard lives in the second half.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "we'll cache it" stand on its own. "You cache a user's profile, then they update their email. The next read still serves the old address from cache. Walk me through exactly what happened on the write, and what you'd change so the cache and the database don't disagree." That question — consistency between two copies of the truth — is the one a memorised "use Redis" answer can never survive, and the one this topic is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds on Zynter are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: you name a strategy, and the next sentence out of the interviewer's mouth attacks its weakest point — staleness, write latency, a dropped write, or a stampede.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the interviewer is really testing
&lt;/h3&gt;

&lt;p&gt;A caching answer is read on the same four dimensions as any design decision. &lt;strong&gt;Problem solving&lt;/strong&gt; — did you pick a strategy that fits the read/write mix, or reach for the first one you remember? &lt;strong&gt;Communication&lt;/strong&gt; — can you say in one sentence why cache-aside tolerates a cache outage but write-back risks losing data? &lt;strong&gt;Technical execution&lt;/strong&gt; — do you handle invalidation on writes, and do you have a concrete fix for a hot key expiring under load? &lt;strong&gt;Time management&lt;/strong&gt; — did you spend your words on the consistency and failure story, not on reciting that Redis is in-memory? Reading this guide teaches the strategies; it does not teach you to defend one while someone pushes on the stale read.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read strategies: cache-aside and read-through
&lt;/h2&gt;

&lt;p&gt;Start with reads, because that is where a cache earns its keep. The question a read strategy answers is: &lt;em&gt;who is responsible for loading data into the cache on a miss&lt;/em&gt; — your application, or the cache itself?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cache-aside (lazy loading)&lt;/strong&gt; is the default and the one to reach for first. The application talks to both the cache and the database and owns the logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On a read, check the cache. On a &lt;strong&gt;hit&lt;/strong&gt;, return it. On a &lt;strong&gt;miss&lt;/strong&gt;, read from the database, write the value into the cache, and return it.&lt;/li&gt;
&lt;li&gt;Only data that is actually requested is ever cached — the cache stays populated with the working set, nothing wasted.&lt;/li&gt;
&lt;li&gt;It is &lt;strong&gt;resilient&lt;/strong&gt;: if the cache is down, the application just reads the database and keeps serving. The cache is an optimisation, not a dependency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its costs are the two you must name out loud: the &lt;strong&gt;first read of any key is always a miss&lt;/strong&gt; (a cold cache means one slow request per key), and because the application writes the cache separately from the database, the two can &lt;strong&gt;drift out of sync&lt;/strong&gt; unless you invalidate on writes. Cache-aside is what most systems use because the failure mode is graceful — a stale or cold cache degrades latency, not correctness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-through&lt;/strong&gt; moves the loading logic &lt;em&gt;into&lt;/em&gt; the cache layer. The application only ever talks to the cache; on a miss the cache itself fetches from the database, stores the value, and returns it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The application code is simpler — one place to read from, no miss-handling branch.&lt;/li&gt;
&lt;li&gt;The load logic lives in the cache (or a library in front of it), so it is consistent across every caller.&lt;/li&gt;
&lt;li&gt;But the cache is now a &lt;strong&gt;hard dependency&lt;/strong&gt; in the read path, and the first-read-miss latency still exists — it just moved.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say the distinction crisply: &lt;em&gt;cache-aside puts the application in charge and survives a cache outage; read-through hides the database behind the cache for simpler code but makes the cache load-bearing.&lt;/em&gt; For most interview designs, cache-aside is the safe default — reach for read-through when you want the loading logic centralised and can accept the cache as a dependency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3e7kpuhhypghg2akmave.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3e7kpuhhypghg2akmave.jpeg" alt="A diagram of the cache-aside read path showing an application that first checks an in-memory cache, returns immediately on a hit, and on a miss reads from the database, writes the value back into the cache, and then returns it, with read-through shown alongside as the same flow but with the cache itself performing the database load." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Write strategies: write-through, write-back and write-around
&lt;/h2&gt;

&lt;p&gt;Reads decide how the cache fills; writes decide whether it ever lies. The follow-up here is the sharp one — &lt;em&gt;you have the data in two places now; on a write, how do you keep them from disagreeing, and what are you willing to lose?&lt;/em&gt; Each strategy answers differently, and the difference is a trade between write latency, consistency, and durability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write-through&lt;/strong&gt; — write the cache and the database together, synchronously, as part of the same operation. The cache is always consistent with the database, so a read right after a write is correct. The cost is &lt;strong&gt;write latency&lt;/strong&gt;: every write pays for both stores, and you may cache data that is never read again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write-back (write-behind)&lt;/strong&gt; — write only the cache, acknowledge immediately, and flush to the database asynchronously (batched, after a delay). Writes are &lt;strong&gt;very fast&lt;/strong&gt; and the database sees far less write load because writes are coalesced. The danger you must state plainly: if the cache node fails before the flush, those &lt;strong&gt;writes are lost&lt;/strong&gt; — this is a durability risk, acceptable for metrics or counters, dangerous for a payment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write-around&lt;/strong&gt; — write straight to the database and &lt;em&gt;skip&lt;/em&gt; the cache; the cache fills only later, on a read (via cache-aside). This avoids flooding the cache with data that is written once and rarely read, but a read immediately after a write is a guaranteed miss.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;On a write&lt;/th&gt;
&lt;th&gt;Consistency&lt;/th&gt;
&lt;th&gt;Write latency&lt;/th&gt;
&lt;th&gt;Risk / best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-through&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;cache &lt;strong&gt;and&lt;/strong&gt; DB, synchronously&lt;/td&gt;
&lt;td&gt;strong — cache matches DB&lt;/td&gt;
&lt;td&gt;higher (two writes)&lt;/td&gt;
&lt;td&gt;caches write-once-read-never data; safe default when reads follow writes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-back&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;cache now, DB async later&lt;/td&gt;
&lt;td&gt;eventual — DB lags the cache&lt;/td&gt;
&lt;td&gt;lowest&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;data loss if the cache dies before flush&lt;/strong&gt;; best for high-write, loss-tolerant data (counters, metrics)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write-around&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DB only; cache fills on read&lt;/td&gt;
&lt;td&gt;DB is the truth; cache may be cold&lt;/td&gt;
&lt;td&gt;normal DB write&lt;/td&gt;
&lt;td&gt;read-after-write is a miss; best for write-heavy data read rarely&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that scores: &lt;em&gt;write-through buys consistency with write latency, write-back buys speed by risking durability, and write-around keeps write-once data out of the cache — so I'd pair cache-aside reads with write-through for a profile a user edits and reads back, and only reach for write-back where losing a few recent writes is acceptable.&lt;/em&gt; Naming the pairing — a read strategy &lt;strong&gt;and&lt;/strong&gt; a write strategy together — is what separates a real answer from "use Redis."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d4toqc3gsee76oqmei5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7d4toqc3gsee76oqmei5.jpeg" alt="A comparison diagram of three write strategies: write-through writing to the cache and database synchronously, write-back writing to the cache and flushing to the database asynchronously with a data-loss warning if the cache fails first, and write-around writing only to the database while the cache fills later on a read, each annotated with its consistency and latency trade-off." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Pick and defend a caching strategy out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Eviction and the hard problems: invalidation, stampede and hot keys
&lt;/h2&gt;

&lt;p&gt;A cache is finite, so you cannot keep everything — and the moment data lives in two places, keeping them honest becomes the hard part. This section is where most of the follow-ups land, because it is where caches break in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Eviction policy&lt;/strong&gt; decides what to drop when the cache is full. Name the common ones and when each fits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LRU (least recently used)&lt;/strong&gt; — evict what has not been touched longest. The sensible default: recently used data is likely to be used again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LFU (least frequently used)&lt;/strong&gt; — evict what is accessed least often. Better when popularity is stable over time, but it clings to old favourites and is costlier to track.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FIFO&lt;/strong&gt; — evict the oldest inserted, regardless of use. Simple, rarely the best fit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTL (time to live)&lt;/strong&gt; — expire entries after a fixed age. Orthogonal to the above and almost always used &lt;em&gt;with&lt;/em&gt; them: TTL bounds staleness, LRU bounds size.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the three failures the interviewer will push on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cache invalidation&lt;/strong&gt; — the famous hard problem: when the database changes, the cache must not keep serving the old value. The options are to &lt;strong&gt;invalidate&lt;/strong&gt; (delete the key on write, so the next read re-loads it) or &lt;strong&gt;update&lt;/strong&gt; the cache on write (write-through). Deletion is simpler and the usual choice; the trap is the read-modify race, where a stale read repopulates the cache just after a write deleted it. Short TTLs are the backstop that bounds how long any staleness can last.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache stampede (thundering herd)&lt;/strong&gt; — a single very popular key expires, and in the same instant thousands of concurrent requests all miss, all hit the database, and all try to recompute the same value — a self-inflicted load spike that can take the database down. The fixes to name: &lt;strong&gt;request coalescing&lt;/strong&gt; (a lock or single-flight so only one request recomputes while the rest wait for the result), &lt;strong&gt;staggered TTLs with jitter&lt;/strong&gt; (so hot keys do not all expire together), &lt;strong&gt;stale-while-revalidate&lt;/strong&gt; (serve the slightly stale value while one request refreshes it in the background), and &lt;strong&gt;pre-warming&lt;/strong&gt; keys you know will be hot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot keys&lt;/strong&gt; — one key (a celebrity's profile, a viral post) draws so much traffic it overwhelms the single cache node holding it. The fixes: &lt;strong&gt;replicate&lt;/strong&gt; the hot key across nodes, add a tiny &lt;strong&gt;local in-process cache&lt;/strong&gt; in front of the shared cache, or shard the key's value.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: eviction keeps the cache &lt;em&gt;small&lt;/em&gt;, TTL keeps it &lt;em&gt;fresh&lt;/em&gt;, invalidation keeps it &lt;em&gt;honest&lt;/em&gt;, and coalescing keeps a &lt;em&gt;miss&lt;/em&gt; from becoming an &lt;em&gt;outage&lt;/em&gt;. Those are four different jobs and a strong answer names which one each mechanism does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3ouqjp9feem5xgt2sua.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn3ouqjp9feem5xgt2sua.jpeg" alt="A diagram of a cache stampede and its fixes, showing a single hot key expiring and thousands of simultaneous requests all missing and hitting the database at once as a thundering herd, alongside the mitigations: request coalescing with a single-flight lock so only one request recomputes, staggered time-to-live with jitter, stale-while-revalidate serving the old value during a background refresh, and replicating the hot key across cache nodes." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend your invalidation and stampede plan out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Where caches live: a layered view
&lt;/h2&gt;

&lt;p&gt;"Add a cache" is ambiguous until you say &lt;em&gt;which&lt;/em&gt; cache, because caching happens at every layer between the user and the database, and a strong answer places its cache deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Client / browser cache&lt;/strong&gt; — the response never leaves the user's device; the cheapest hit of all, controlled by cache headers. Best for static, user-specific data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN / edge cache&lt;/strong&gt; — static assets and cacheable responses served from a point of presence near the user; absorbs read traffic before it reaches your servers at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application / distributed cache&lt;/strong&gt; — a shared in-memory store like &lt;strong&gt;Redis&lt;/strong&gt; or &lt;strong&gt;Memcached&lt;/strong&gt; sitting between the app servers and the database. This is the cache most interview discussions mean: it holds the hot working set, is shared across app instances, and is where cache-aside and write-through play out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database cache&lt;/strong&gt; — the database's own buffer pool and query cache. Real, but not something you design; mention it, do not lean on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The useful framing: traffic should be absorbed as &lt;strong&gt;early&lt;/strong&gt; as possible — a request served from the browser or CDN is one your application and database never pay for. The distributed cache catches what gets through. Naming the layer you mean, rather than a generic "cache" box, is a small signal that reads as experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to talk about caching in an interview
&lt;/h2&gt;

&lt;p&gt;The probes from every section rhyme. Master these five and you can defend a caching decision in any design, not just this checklist.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"You've added a cache — cache-aside or read-through, and why?"&lt;/strong&gt; → Cache-aside by default: the app owns the miss logic and the system survives a cache outage because it can still read the database; read-through when I want the load logic centralised and can accept the cache as a dependency. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"On a write, how do you keep the cache and database consistent?"&lt;/strong&gt; → Invalidate the key on write (delete it so the next read re-loads), or use write-through to update both synchronously; back it with a short TTL so any missed invalidation self-heals, and watch for the read-modify race that repopulates stale data. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Write-through, write-back or write-around here?"&lt;/strong&gt; → Write-through for data read back soon after a write (consistency worth the latency); write-back only where speed matters and losing a few recent writes is acceptable; write-around for write-once, rarely-read data. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A hot key expires and thousands of reads hit the database at once — what happens?"&lt;/strong&gt; → A stampede that can take the database down; prevent it with request coalescing so one request recomputes while others wait, staggered TTLs with jitter, and stale-while-revalidate. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The cache is full — what gets evicted?"&lt;/strong&gt; → LRU as the default, LFU when popularity is stable, with a TTL layered on to bound staleness regardless of the eviction policy. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each maps to a debrief dimension. Reading them teaches the answers; it does not teach you to deliver them while someone interrupts with the stale read — that only comes from saying them out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a short plan
&lt;/h2&gt;

&lt;p&gt;You do not need a new topic for every design. You need the caching vocabulary automatic, so when a design hits its read bottleneck you reach for the right pair without thinking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 1:&lt;/strong&gt; Learn the read strategies (cache-aside vs read-through) and the write strategies (write-through, write-back, write-around) until you can draw each flow and state its one-sentence trade-off from memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Drill the failures — invalidation on writes, the thundering-herd stampede and its four fixes, and hot keys. These are where the follow-ups live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; Place caching inside a real design (a feed, a catalogue, a URL shortener): pick a read strategy and a write strategy together, choose an eviction policy and TTL, and name the layer. Then do it out loud, timed, with someone pushing on the stale read and the hot key.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explainer feeds the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — caching is a decision inside almost every one of them, so getting it automatic pays off across the whole list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the cache-aside (lazy loading) strategy?
&lt;/h3&gt;

&lt;p&gt;Cache-aside is the most common caching pattern, where the application manages the cache directly. On a read, the application first checks the cache; on a hit it returns the cached value, and on a miss it reads from the database, writes that value into the cache, and returns it. Only data that is actually requested ever gets cached, so the cache naturally holds the working set. Its main advantages are resilience — if the cache goes down, the application simply reads from the database and keeps serving — and efficiency. Its costs are that the first read of any key is always a miss, and that the application must invalidate or update the cache on writes, or the two copies drift out of sync.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache-aside vs write-through — what's the difference?
&lt;/h3&gt;

&lt;p&gt;They answer different questions, so they are often used together rather than being alternatives. Cache-aside is a &lt;strong&gt;read&lt;/strong&gt; strategy: it decides how the cache gets populated on a miss (the application loads it). Write-through is a &lt;strong&gt;write&lt;/strong&gt; strategy: it decides what happens on a write (update the cache and the database synchronously so they stay consistent). A very common real-world combination is cache-aside for reads plus write-through or explicit invalidation on writes, so reads are fast and lazily populated while writes keep the cache honest. Confusing the two — treating them as competing options — is a common interview slip.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is write-back (write-behind) caching and when is it risky?
&lt;/h3&gt;

&lt;p&gt;Write-back caching writes data to the cache and acknowledges the write immediately, then flushes it to the database asynchronously, usually batched after a short delay. This makes writes very fast and greatly reduces write load on the database, because many writes to the same key can be coalesced into one. The risk is durability: if the cache node fails before the pending writes are flushed to the database, those writes are lost permanently. That makes write-back a good fit for high-volume, loss-tolerant data such as counters, metrics or view counts, and a poor fit for data you cannot afford to lose, such as a payment or an order.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you handle cache invalidation?
&lt;/h3&gt;

&lt;p&gt;Cache invalidation keeps the cache from serving stale data after the database changes. The two main approaches are to delete the key on a write — so the next read misses and re-loads the fresh value from the database — or to update the cache on the write, as write-through does. Deletion is simpler and the more common choice. A short time-to-live (TTL) is almost always used as a backstop, so that even if an invalidation is missed, any staleness is bounded to the TTL window. The subtle trap is the read-modify race, where a concurrent stale read repopulates the cache just after a write deleted the key; short TTLs and careful ordering reduce its impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a cache stampede and how do you prevent it?
&lt;/h3&gt;

&lt;p&gt;A cache stampede, or thundering herd, happens when a single popular key expires and thousands of concurrent requests all miss at the same instant, all hit the database, and all try to recompute the same value at once — a self-inflicted load spike that can overwhelm the database. The standard preventions are request coalescing (a lock or single-flight mechanism so only one request recomputes the value while the others wait for the result), staggering TTLs with random jitter so hot keys do not all expire together, serving the slightly stale value while one request refreshes it in the background (stale-while-revalidate), and pre-warming keys you know will be hot before they are needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;Reading about caching strategies is not the same as defending one while an interviewer pushes on the stale read. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a design on a shared canvas, and a voice interviewer follows up one question at a time ("on a write, how do the cache and database stay consistent?", "a hot key expires — what happens?"), then gives you a debrief on problem solving, communication, technical execution and time management. The score is a practice signal, not a hiring decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can recite cache-aside and write-through. The round is won on defending the stale read out loud.&lt;/h3&gt;

&lt;p&gt;Pick an HLD system design round, add your cache to the design on the canvas, and defend it out loud to a voice interviewer that keeps asking "on a write, how do the cache and database stay consistent?" and "a hot key just expired and ten thousand reads hit the database — what happens?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise an HLD system design round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>designsystem</category>
      <category>dataengineering</category>
      <category>walkin</category>
    </item>
    <item>
      <title>SQL vs NoSQL: how to actually choose in a system design interview</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:55:42 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/sql-vs-nosql-how-to-actually-choose-in-a-system-design-interview-324o</link>
      <guid>https://dev.to/gowthampotureddi/sql-vs-nosql-how-to-actually-choose-in-a-system-design-interview-324o</guid>
      <description>&lt;h2&gt;
  
  
  SQL vs NoSQL: how to actually choose in a system design interview
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;SQL vs NoSQL&lt;/strong&gt; question is the one that stalls the middle of a system design interview. You have drawn the boxes, sketched the API, and then the interviewer taps the data store and asks the quiet question: &lt;em&gt;"What database are you using here, and why?"&lt;/em&gt; — and a surprising number of strong candidates answer with a preference instead of a reason. They say "I'd use NoSQL, it scales better," or "SQL, it's more reliable," as if one of them simply won the argument years ago. It did not. The honest answer is that &lt;strong&gt;SQL and NoSQL are not competitors for the same job&lt;/strong&gt; — they are different tools whose guarantees fit different workloads, and the whole skill the round is testing is whether you can look at &lt;em&gt;this&lt;/em&gt; system's access patterns and pick the store that fits.&lt;/p&gt;

&lt;p&gt;That reframe is worth saying out loud early: the question is not "which database is better," it is "which database fits the way this system reads and writes its data." A good &lt;strong&gt;system design interview&lt;/strong&gt; answer treats &lt;strong&gt;database design&lt;/strong&gt; as a decision you derive — from the shape of the data, the consistency the product needs, and the scale it has to reach — not a brand you reach for out of habit. This guide gives you the three things you need to do that on the clock: what a relational (SQL) database actually guarantees, what the four families of NoSQL stores trade those guarantees for, and a requirement-to-choice framework you can walk through out loud while someone pushes back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wcie5raet2jdm6qia2h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2wcie5raet2jdm6qia2h.jpeg" alt="Title card for a system design interview guide on choosing SQL versus NoSQL, showing a relational table with rows and columns joined to another table on one side and a set of flexible NoSQL records (a key-value pair, a JSON document, a wide-column row and a small graph of nodes) on the other, with a decision arrow in the middle pointing from an access patterns label to whichever store fits, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "SQL or NoSQL?" comes up in every system design interview
&lt;/h2&gt;

&lt;p&gt;Almost every "design a {system}" round reaches a data-model step, and the moment you draw a database box the interviewer wants to know what kind it is. It is a favourite probe because it is a fast, reliable way to tell a memorised answer from real understanding: the candidate who recites "NoSQL scales horizontally" has read a blog; the candidate who says "this is a write-heavy event stream with simple key access and no transactions, so a wide-column store fits, and here's what I give up to get that" has actually thought about the workload. The question is small, but the reasoning behind it is the whole game.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely accepts a one-word answer. "You said you'd store this in NoSQL — fine, but tell me &lt;em&gt;why&lt;/em&gt;, tied to how this system reads and writes, not to which database you like. And what do you lose by not using a relational store here?" That follow-up — justify the store from the access pattern, and name the trade-off — is the one a copied answer can never rehearse, and the one this decision is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: you make a database call, and then you have to defend it against "why not the other one?"&lt;/p&gt;

&lt;h3&gt;
  
  
  What the interviewer is really testing
&lt;/h3&gt;

&lt;p&gt;The score is not "did you pick the database I was thinking of." A good data-store decision reads on four dimensions, and every part of this guide maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reason from the system's access patterns to a store, instead of defaulting to a favourite), &lt;strong&gt;communication&lt;/strong&gt; (can you say in one sentence why this store fits this workload and name what you trade for it), &lt;strong&gt;technical execution&lt;/strong&gt; (do you actually understand ACID vs eventual consistency, joins, denormalization, sharding and the NoSQL families), and &lt;strong&gt;time management&lt;/strong&gt; (did you make the call quickly and defensibly so the round's minutes go to the harder parts of the design). Reading a comparison table teaches you the facts; it does not teach you to deliver the decision on the clock while someone asks "why not the other one?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What a relational (SQL) database gives you
&lt;/h2&gt;

&lt;p&gt;Start with the relational store, because it is the sensible default and because naming what it gives you makes the NoSQL trade-offs legible. A SQL database — Postgres, MySQL, SQL Server — stores data in &lt;strong&gt;tables with a fixed, enforced schema&lt;/strong&gt;, and it hands you four things that are genuinely hard to rebuild yourself.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Relational gives you&lt;/th&gt;
&lt;th&gt;What it means in the round&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;A declared schema&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Columns and types are defined and enforced; bad data is rejected at write time, so every reader can trust the shape.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Relationships and joins&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data is &lt;strong&gt;normalised&lt;/strong&gt; across tables and recombined at read time with joins, so a fact lives in one place and is never duplicated or left stale.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ACID transactions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A unit of work is &lt;strong&gt;atomic, consistent, isolated and durable&lt;/strong&gt; — debit one row and insert another as all-or-nothing, with strong consistency so a read after a write sees the write.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ad-hoc queries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You do &lt;strong&gt;not&lt;/strong&gt; have to know the query in advance — filters, aggregations and joins over any column are available, which is priceless while a product's questions are still changing.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The cost is the scaling story. A relational database scales &lt;strong&gt;vertically&lt;/strong&gt; with ease (a bigger machine) and horizontally with &lt;strong&gt;read replicas&lt;/strong&gt; for read-heavy load, but scaling &lt;strong&gt;writes&lt;/strong&gt; beyond one machine means &lt;strong&gt;sharding&lt;/strong&gt;, and once data is split across shards the very things you came for — cross-row joins and multi-row transactions — become hard, because they now cross a network. None of that disqualifies SQL; it just means the honest sentence is: &lt;em&gt;relational is the right default for structured, related data that needs transactions and strong consistency, and it scales a long way before sharding writes becomes the pain point.&lt;/em&gt; Money, orders, inventory, bookings — anything where "we lost a write" or "two rows disagree" is unacceptable — starts here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnnffvdsszzh9m0ujekyt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnnffvdsszzh9m0ujekyt.jpeg" alt="A diagram of what a relational database gives you: two tables, a users table and an orders table, connected by a foreign-key join on user id, with a callout that a single transaction updating both is atomic and consistent (ACID), and a note that the schema is fixed and enforced, ad-hoc queries are supported, and the store scales vertically and with read replicas, sharding taking real effort." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The NoSQL families — and what each one trades
&lt;/h2&gt;

&lt;p&gt;The first mistake to avoid out loud is treating &lt;strong&gt;NoSQL&lt;/strong&gt; as one thing. It is an umbrella over four quite different families, each optimised for a different access pattern. Name the family, not just "NoSQL," and you have already shown you understand the space.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Family&lt;/th&gt;
&lt;th&gt;Optimised for&lt;/th&gt;
&lt;th&gt;Example stores&lt;/th&gt;
&lt;th&gt;Reach for it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Key-value&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;O(1) get/put by a single key&lt;/td&gt;
&lt;td&gt;Redis, DynamoDB&lt;/td&gt;
&lt;td&gt;sessions, caches, feature flags, rate-limit counters — you always look up by one key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Document&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;self-contained JSON-like documents, flexible schema&lt;/td&gt;
&lt;td&gt;MongoDB, Couchbase&lt;/td&gt;
&lt;td&gt;catalogs, user profiles, content — the record is read and written as a whole and the shape varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wide-column&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;enormous write throughput, partition + clustering keys&lt;/td&gt;
&lt;td&gt;Cassandra, HBase, Bigtable&lt;/td&gt;
&lt;td&gt;event streams, time-series, messaging history — write-heavy, queried by a known key range&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;traversing relationships (edges) fast&lt;/td&gt;
&lt;td&gt;Neo4j, Neptune&lt;/td&gt;
&lt;td&gt;social graphs, recommendations, fraud rings — the questions are about &lt;em&gt;connections&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under the four families runs one common thread, and it is the sentence that scores: NoSQL stores buy &lt;strong&gt;horizontal scale-out, a flexible or absent schema, and predictable low-latency access&lt;/strong&gt; by giving up some of what relational guarantees — usually &lt;strong&gt;strong consistency&lt;/strong&gt; (many default to &lt;strong&gt;eventual consistency&lt;/strong&gt;, the "BASE" posture rather than ACID, though several now offer stronger options), &lt;strong&gt;joins&lt;/strong&gt;, and &lt;strong&gt;multi-row transactions&lt;/strong&gt;. Because there are no joins, you &lt;strong&gt;denormalize&lt;/strong&gt;: you duplicate data across records so each query reads from one place. The governing rule, worth stating plainly, is the inversion of the relational mindset — &lt;strong&gt;in NoSQL you design the data around the queries, not the queries around the data.&lt;/strong&gt; That is the real cost and the real power: you must know your access patterns up front, and in exchange you get a store that scales those patterns almost linearly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynzr6hojpp64ikmo3i95.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fynzr6hojpp64ikmo3i95.jpeg" alt="A comparison of the four NoSQL families shown as four cards: key-value (a single key mapping to a value, example Redis or DynamoDB, for sessions and caches), document (a nested JSON document, example MongoDB, for catalogs and profiles), wide-column (a partition key with many clustering columns, example Cassandra, for events and time-series at high write volume), and graph (nodes connected by edges, example Neo4j, for social graphs and recommendations), with a shared footer noting the common trade-off of horizontal scale and flexible schema in exchange for eventual consistency and limited joins." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend a SQL-vs-NoSQL call out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  How to choose: a decision framework
&lt;/h2&gt;

&lt;p&gt;Now the part the round is actually scoring — turning the two toolkits into a decision. The move is to refuse the "which is better" framing and answer by requirement. Ask three questions of the data and let the answers point at a store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If the system needs…&lt;/th&gt;
&lt;th&gt;Lean toward&lt;/th&gt;
&lt;th&gt;Because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;multi-row &lt;strong&gt;transactions&lt;/strong&gt;, &lt;strong&gt;strong consistency&lt;/strong&gt;, rich &lt;strong&gt;relationships&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Relational (SQL)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ACID and joins are built in; correctness beats raw write scale here (payments, orders, inventory, bookings)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;massive write volume&lt;/strong&gt;, simple &lt;strong&gt;key-based&lt;/strong&gt; access, &lt;strong&gt;horizontal&lt;/strong&gt; scale from day one&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;NoSQL&lt;/strong&gt; — pick the family by access pattern&lt;/td&gt;
&lt;td&gt;scale-out and low-latency key access without the sharding pain of SQL (feeds, events, sessions, metrics, large catalogs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fast traversal of &lt;strong&gt;connections&lt;/strong&gt; (friends-of-friends, paths)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Graph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;relationship queries that would be many self-joins in SQL become native traversals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unknown or shifting access patterns, heavy &lt;strong&gt;ad-hoc&lt;/strong&gt; reporting&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Relational (SQL)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;you can ask new questions without re-modelling; NoSQL punishes queries you didn't design for&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then say the thing that shows range: &lt;strong&gt;most real systems are polyglot.&lt;/strong&gt; A payments product keeps its ledger in a relational store for transactions, caches hot reads in a key-value store, and streams its audit events into a wide-column store — three databases, each chosen for one access pattern, not one database forced to do everything. Reaching for "I'd use a relational store for the transactional core and a key-value cache in front of it" is almost always a stronger answer than picking a single winner.&lt;/p&gt;

&lt;p&gt;One honest nuance keeps you out of trouble: the line between the two has blurred. Modern relational databases and &lt;strong&gt;NewSQL&lt;/strong&gt; systems (CockroachDB, Spanner) scale horizontally, and several NoSQL stores now offer transactions and tunable consistency. So do not over-claim — "NoSQL can't do transactions" or "SQL can't scale" are both out of date. The durable skill, and the one the interviewer is grading, is &lt;strong&gt;reasoning from access patterns to a store and naming the trade-off&lt;/strong&gt;, which survives whatever any single product adds next quarter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jta76t1bfwvpti4xuge.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1jta76t1bfwvpti4xuge.jpeg" alt="A decision-framework flowchart for choosing a database by requirement: start from the system's access patterns and requirements; if you need multi-row transactions, strong consistency and rich relationships, choose a relational SQL store; if you need massive write volume, simple key-based access and horizontal scale, choose NoSQL and pick the family by access pattern (key-value, document, wide-column); if the core workload is relationship traversal, choose a graph database; with a note that most real systems are polyglot, using a relational core plus NoSQL stores for scale-out and caching." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Walk the data-model step of an HLD round out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  A worked example out loud
&lt;/h2&gt;

&lt;p&gt;Put the framework to work on a system with two very different stores inside it, because that is what interviewers like to hand you. Say you are designing an e-commerce app, and the interviewer asks where two things live: the &lt;strong&gt;orders&lt;/strong&gt; and the &lt;strong&gt;activity feed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;orders&lt;/strong&gt;, reason out loud: an order touches several related entities — a customer, line items, a payment, inventory — and it must be &lt;strong&gt;transactional&lt;/strong&gt; (take payment, decrement stock and create the order as all-or-nothing) and &lt;strong&gt;strongly consistent&lt;/strong&gt; (a customer who just paid must see their order). That is a textbook relational workload: a &lt;strong&gt;SQL&lt;/strong&gt; store, normalised across tables, with an ACID transaction around the checkout. Write volume is moderate and correctness is non-negotiable, so the relational guarantees are exactly what you want and sharding writes is a problem for much later.&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;activity feed&lt;/strong&gt; — every view, click and add-to-cart streaming in — the shape is the opposite: a &lt;strong&gt;firehose of simple, append-only events&lt;/strong&gt;, queried by user or by time range, with no transactions and no joins, and it has to absorb bursts and scale horizontally from the start. Losing one feed event is survivable; losing an order is not. That is a &lt;strong&gt;wide-column NoSQL&lt;/strong&gt; workload (Cassandra-style), partitioned by user id, denormalized so each read hits one partition. Two stores, one system, each chosen from its access pattern — and now you can name the polyglot design instead of forcing both into one database. That contrast, reasoned aloud, is worth more than any single "right" answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your relational database can't keep up with the write volume anymore — what do you do &lt;em&gt;before&lt;/em&gt; you rip it out for NoSQL?" First, the cheap wins: add read replicas to offload reads, put a cache in front of hot queries, and batch or queue writes to smooth bursts. Then vertical scale, then functional partitioning (move a heavy table to its own database). Only when writes genuinely exceed one machine's ceiling do you shard — and at that point, if the workload is simple key-based access without cross-row transactions, a NoSQL store that shards natively may be less painful than sharding the relational one by hand. The point: migrating stores is the last lever, not the first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How to answer "SQL or NoSQL?" in an interview
&lt;/h2&gt;

&lt;p&gt;Scan the probes and they rhyme. Master these five and you can defend a database call for a system drawn differently from any here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Why this store, and not the other one?"&lt;/strong&gt; → Name the access pattern first (transactional-and-related vs write-heavy-simple-key vs traversal), then the store that fits it, then the one thing you trade. Never lead with a brand preference. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What do you give up by choosing NoSQL here?"&lt;/strong&gt; → Usually joins, multi-row transactions and strong consistency; you denormalize and design the data around the queries, so access patterns you didn't plan for become expensive. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Are NoSQL databases ACID or not?"&lt;/strong&gt; → Classically BASE / eventually consistent, favouring availability and scale; but several (DynamoDB, MongoDB) now offer transactions and tunable consistency, so say "it depends on the store and the configuration," not a blanket no. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Your SQL database is overloaded on writes — now what?"&lt;/strong&gt; → Replicas and caching for reads first, then vertical scale and functional partitioning, then sharding; switching to NoSQL is the last resort, justified only when the workload is simple-key and truly beyond one machine. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Would you really use two databases in one system?"&lt;/strong&gt; → Yes — polyglot persistence is normal: a relational core for the transactional data plus key-value/wide-column stores for caching and scale-out, each chosen for one access pattern. (technical execution)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a short plan
&lt;/h2&gt;

&lt;p&gt;You do not need to memorise a feature matrix. You need the guarantees and the framework automatic, so the data-model step of any design is a quick, defensible call instead of a stall.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 1:&lt;/strong&gt; Learn what relational gives you (schema, joins, ACID, ad-hoc queries) and its scaling story (vertical, replicas, sharding-with-effort). Say, in one sentence each, when it is the right default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Learn the four NoSQL families and one example store and use-case for each, plus the common trade-off (horizontal scale + flexible schema vs eventual consistency + limited joins) and the "design around the queries" rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; Drill the decision framework — take five systems (a URL shortener, a chat app, a payments ledger, a social feed, a recommendations engine) and reason each to a store (or a polyglot mix) out loud, naming the trade-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 4:&lt;/strong&gt; Do the data-model step of a couple of full HLD rounds against the clock, narrating the choice and defending it against "why not the other one?" This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is one of the concept pages behind the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;system design interview questions&lt;/a&gt; track — once the SQL-vs-NoSQL call is reflex, it stops eating the minutes you need for the rest of the design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between SQL and NoSQL in one sentence?
&lt;/h3&gt;

&lt;p&gt;SQL (relational) databases store data in tables with a fixed, enforced schema and give you relationships, joins, ACID transactions and strong consistency, which makes them the right default for structured, related data that must stay correct; NoSQL is an umbrella over key-value, document, wide-column and graph stores that trade some of those guarantees — often joins, multi-row transactions and strong consistency — for a flexible schema, horizontal scale-out and predictable low-latency access to the specific patterns you designed for. The one-liner: SQL fits transactional, related data; NoSQL fits a known access pattern that needs to scale horizontally.&lt;/p&gt;

&lt;h3&gt;
  
  
  When should I use NoSQL instead of SQL?
&lt;/h3&gt;

&lt;p&gt;Reach for NoSQL when the workload has a simple, known access pattern that needs to scale horizontally from the start — massive write volume, key-based lookups, flexible or evolving record shapes — and does not need multi-row transactions or joins. Event streams, activity feeds, session stores, caches, metrics and very large catalogs are classic fits, and you pick the family by the access pattern (key-value for pure lookups, document for whole-record reads, wide-column for write-heavy time-series, graph for relationship traversal). If the data is highly related and needs transactions and strong consistency, or if the queries are still changing and you need ad-hoc reporting, prefer SQL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are NoSQL databases ACID or eventually consistent?
&lt;/h3&gt;

&lt;p&gt;Classically, NoSQL stores favour the BASE model — basically available, soft state, eventually consistent — trading strong consistency for availability and horizontal scale, which is why "eventual consistency" is the stereotype. But it is no longer a clean split: several NoSQL databases now offer transactions and tunable or strong consistency (DynamoDB supports transactions, MongoDB supports multi-document transactions, and systems like Spanner and CockroachDB give distributed ACID). So the accurate interview answer is "it depends on the store and how you configure it," not a blanket "NoSQL isn't ACID."&lt;/p&gt;

&lt;h3&gt;
  
  
  Can you use both SQL and NoSQL in the same system?
&lt;/h3&gt;

&lt;p&gt;Yes, and it is the normal design at scale — it is called polyglot persistence. You pick a store per access pattern rather than forcing one database to do everything: a relational store for the transactional core (orders, payments), a key-value store for caching and sessions, a wide-column store for high-volume event or time-series data, and perhaps a graph store for relationship-heavy features. Saying this in an interview signals maturity, because it shows you treat the database as a per-workload decision instead of a single religious choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does NoSQL really scale better than SQL?
&lt;/h3&gt;

&lt;p&gt;Not universally — it scales differently. NoSQL stores are built to scale writes horizontally across many nodes out of the box, which is genuinely easier than sharding a relational database by hand, so for write-heavy, simple-key workloads they scale more cheaply. But relational databases scale a long way with vertical hardware, read replicas and caching, and modern NewSQL systems scale horizontally too. The honest statement is that NoSQL makes horizontal write scaling easy by giving up joins and transactions; SQL makes correctness and flexible querying easy and needs more effort to scale writes. Match the strength to the workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise answering SQL vs NoSQL out loud?
&lt;/h3&gt;

&lt;p&gt;Reading a comparison is not the same as defending a database call while someone asks "why not the other one?" On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you draw the design on a shared canvas, reach the data-model step, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can recite the comparison table. The round is won on reasoning to a store out loud.&lt;/h3&gt;

&lt;p&gt;Pick any system design round, draw it on the canvas, and when you reach the database box defend your SQL-or-NoSQL call to a voice interviewer that keeps asking "why this store, not the other one?" and "what do you give up here?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise an HLD round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>walkin</category>
      <category>designsystem</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Database sharding and partitioning: the scaling move an interviewer makes you justify</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:53:03 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/database-sharding-and-partitioning-the-scaling-move-an-interviewer-makes-you-justify-34l2</link>
      <guid>https://dev.to/gowthampotureddi/database-sharding-and-partitioning-the-scaling-move-an-interviewer-makes-you-justify-34l2</guid>
      <description>&lt;h2&gt;
  
  
  Database sharding and partitioning: the scaling move an interviewer makes you justify
&lt;/h2&gt;

&lt;p&gt;Every system design answer eventually hits the same wall: one database is not enough. The moment your design has to hold more data or absorb more writes than a single machine can, &lt;strong&gt;database sharding&lt;/strong&gt; stops being a buzzword and becomes the thing you have to reason about out loud. And &lt;strong&gt;sharding and partitioning&lt;/strong&gt; are where an interviewer separates the candidates who scale a database by reflex — "we'll just shard it" — from the ones who can say &lt;em&gt;why now&lt;/em&gt;, &lt;em&gt;on what key&lt;/em&gt;, and &lt;em&gt;at what cost&lt;/em&gt;. This explainer is about being the second kind.&lt;/p&gt;

&lt;p&gt;The reason this comes up in almost every &lt;strong&gt;system design interview&lt;/strong&gt; is that the trick everyone reaches for first — add read replicas — only scales reads. The instant the bottleneck is write throughput or raw storage, replicas do nothing, and the only way out is &lt;strong&gt;horizontal scaling&lt;/strong&gt;: split the data across many machines so no single one carries it all. That split is partitioning, sharding is partitioning taken across machines, and the quality of your answer is decided by the one decision underneath it — the &lt;strong&gt;shard key&lt;/strong&gt;. Get the vocabulary straight, learn the three ways to split the data, and learn to pick and defend a shard key, and you have the whole topic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6381e1c5ztefzlbbud4b.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6381e1c5ztefzlbbud4b.jpeg" alt="Title card for a system design interview explainer on database sharding and partitioning, showing one overloaded database splitting into several smaller shard databases, each holding a slice of the rows keyed by a shard key, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why sharding comes up in every system design interview
&lt;/h2&gt;

&lt;p&gt;Nearly every "design X" round reaches a point where you have drawn a single database box and the interviewer asks the quiet question that decides the second half of the round: &lt;em&gt;what happens when this one box isn't enough?&lt;/em&gt; A single database server has a ceiling — CPU, memory, disk, and the number of open connections — and once your writes or your dataset cross it, no amount of query tuning or caching moves the ceiling. Read replicas buy you read capacity and nothing else: every replica still has to apply every write, so the write and storage ceiling is unchanged. Sharding is the answer the interviewer is waiting to hear, and then to pressure-test.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your users table is 10 TB and growing, and one box can't hold it or keep up with the writes. How do you shard it, and on what key?" That question is the whole topic in one line — it is not enough to say "shard by user"; you have to justify the key against how the data is read and written, and name what you give up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: you introduce sharding at the right moment, name a strategy, defend a shard key, and then answer the follow-up about the shard that runs hot — out loud, while someone pushes back.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the interviewer is really testing
&lt;/h3&gt;

&lt;p&gt;The score is not "did you say the word sharding." A good system design debrief reads on four dimensions, and every move in this topic maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you recognise the moment a single DB and its replicas are exhausted, and reach for the strategy that fits the access pattern), &lt;strong&gt;communication&lt;/strong&gt; (can you justify the shard key in one sentence — high cardinality, even distribution, aligned with the dominant query — and name the trade-off you accept), &lt;strong&gt;technical execution&lt;/strong&gt; (range vs hash vs directory, consistent hashing to bound remaps, scatter-gather and two-phase commit for cross-shard work), and &lt;strong&gt;time management&lt;/strong&gt; (did you introduce sharding when it was needed rather than as your first move, and avoid rat-holing on resharding mechanics unless pushed). Reading this teaches the ideas; it does not teach you to deliver them on the clock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Partitioning vs sharding: vertical and horizontal
&lt;/h2&gt;

&lt;p&gt;Start by getting the words right, because interviewers notice when they are used loosely. &lt;strong&gt;Partitioning&lt;/strong&gt; is the general act of splitting one logical dataset into smaller parts. It comes in two flavours.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vertical partitioning&lt;/strong&gt; splits by &lt;strong&gt;column or feature&lt;/strong&gt; — you move some columns, or whole tables, into their own store. A user's profile fields live in one place, their billing records in another, their activity log in a third. Each store holds &lt;em&gt;different columns&lt;/em&gt; for the same entities. This is how a monolith's database naturally breaks apart as it grows into services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Horizontal partitioning&lt;/strong&gt; splits by &lt;strong&gt;row&lt;/strong&gt; — every partition has the &lt;strong&gt;same schema&lt;/strong&gt; but holds a different subset of the rows. Users 1–1,000,000 here, users 1,000,001–2,000,000 there. Each partition is a complete, self-contained slice of the table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sharding is horizontal partitioning where the partitions live on separate machines.&lt;/strong&gt; Each shard is its own database, on its own hardware, holding its slice of the rows. That distinction is the point: splitting rows into logical partitions on one server buys you nothing against a capacity ceiling; spreading those partitions across machines is what adds CPU, memory, disk and connections.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hoc784yyg7xxll42i0y.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4hoc784yyg7xxll42i0y.jpeg" alt="A diagram contrasting vertical partitioning, which splits a table's columns or whole tables into separate stores by feature, with horizontal partitioning, which splits the table's rows into subsets with the same schema, and showing that sharding is horizontal partitioning where each subset of rows lives on a separate database machine." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"Read replicas scale reads because every replica is a full copy; they do nothing for write throughput or storage, because each still applies every write and holds all the data. Sharding is the only thing that scales writes and storage, because each shard owns just its slice."&lt;/em&gt; Say that and you have explained not only what sharding is but exactly when it is the right tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharding strategies: range, hash and directory
&lt;/h2&gt;

&lt;p&gt;Once you have decided to shard, the next question is &lt;em&gt;how do you decide which row goes to which shard?&lt;/em&gt; There are three classic strategies, and naming all three before picking one signals range.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Range-based&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Hash-based&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Directory / lookup-based&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;assign contiguous &lt;strong&gt;key ranges&lt;/strong&gt; to shards (A–F, G–M, …)&lt;/td&gt;
&lt;td&gt;shard = a function of &lt;strong&gt;&lt;code&gt;hash(shard_key)&lt;/code&gt;&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;a &lt;strong&gt;lookup table&lt;/strong&gt; maps each key (or key range) to a shard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Range scans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;cheap and local — a range lives on one shard&lt;/td&gt;
&lt;td&gt;expensive — a range is scattered across all shards&lt;/td&gt;
&lt;td&gt;depends on how the table is organised&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load spread&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;risks &lt;strong&gt;hotspots&lt;/strong&gt; — sequential keys (timestamps, auto-increment ids) all land on the newest shard&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;even&lt;/strong&gt; — hashing scatters adjacent keys&lt;/td&gt;
&lt;td&gt;even or uneven, you control it by editing the map&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Adding a shard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;split a range; moves one range's data&lt;/td&gt;
&lt;td&gt;plain &lt;code&gt;hash mod N&lt;/code&gt; &lt;strong&gt;remaps almost everything&lt;/strong&gt; — use &lt;strong&gt;consistent hashing&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;edit the directory; move only what you choose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Main cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;hotspot management&lt;/td&gt;
&lt;td&gt;lost locality; needs consistent hashing&lt;/td&gt;
&lt;td&gt;the directory is an extra dependency and a potential &lt;strong&gt;single point of failure / bottleneck&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things to say out loud about each. &lt;strong&gt;Range-based&lt;/strong&gt; sharding keeps range queries fast because a whole range sits on one shard, which is why time-series and ordered data often use it — but that same property is its trap: if your key increases monotonically, every new write targets the last shard and you have built a hotspot. &lt;strong&gt;Hash-based&lt;/strong&gt; sharding fixes the spread by scattering adjacent keys everywhere, at the price of range scans (a "last 7 days" query now hits every shard); and the naive version, &lt;code&gt;hash(key) mod N&lt;/code&gt;, is a trap of its own, because changing &lt;code&gt;N&lt;/code&gt; when you add a shard remaps almost every key at once — which is exactly the problem &lt;strong&gt;consistent hashing&lt;/strong&gt; solves by moving only a small fraction of keys when the shard count changes. &lt;strong&gt;Directory-based&lt;/strong&gt; sharding is the most flexible — you rebalance by editing a mapping table — but that table is now something every query consults, so it has to be small, cached, and highly available, or it becomes the bottleneck you were trying to escape.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekzimr2wvzql1zuul8ny.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fekzimr2wvzql1zuul8ny.jpeg" alt="A diagram comparing three sharding strategies: range-based sharding assigning contiguous key ranges to shards with a note that sequential keys create a hotspot, hash-based sharding spreading keys evenly by hashing the shard key with a note that it loses range scans and needs consistent hashing to avoid remapping everything, and directory-based sharding routing each key through a lookup table that maps key to shard with a note that the directory is flexible but a single point of failure." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Pick a sharding strategy and defend it out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Choosing a shard key — and the problems sharding creates
&lt;/h2&gt;

&lt;p&gt;The strategy matters, but the decision that actually makes or breaks a sharded design is the &lt;strong&gt;shard key&lt;/strong&gt; — the column (or combination of columns) whose value decides which shard a row lives on. A good shard key has three properties, and you should name them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High cardinality&lt;/strong&gt; — many distinct values, so the data can be divided finely. Sharding on a boolean or a status column gives you at most a handful of buckets and no room to grow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Even access distribution&lt;/strong&gt; — traffic spreads roughly equally across values, so no shard carries far more than its share.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aligned with the dominant query&lt;/strong&gt; — most reads and writes should be answerable from a single shard. If you shard orders by &lt;code&gt;order_id&lt;/code&gt; but almost every query is "all orders for this customer," you have guaranteed that the common path touches every shard.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a key fails the even-distribution test, you get the &lt;strong&gt;hotspot&lt;/strong&gt; — often called the &lt;strong&gt;celebrity problem&lt;/strong&gt;. Shard a social graph by user and most users are fine, but the one account with fifty million followers sends its shard into the ground while the others idle. The fixes are worth naming: a &lt;strong&gt;compound shard key&lt;/strong&gt; (combine the hot dimension with another, e.g. user plus time bucket) to spread one entity's load, splitting the hot key's data across shards deliberately, or putting a &lt;strong&gt;cache&lt;/strong&gt; in front of the hot key so most reads never reach the shard.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"One of your shards is running 5x hotter than the rest. What did you get wrong, and how do you fix it without taking the system down?" The honest answer names the shard key: it concentrated load — a low-cardinality or skewed key, or a celebrity entity. The fix is a better or compound key and a controlled migration of the hot data to rebalance, done online by double-writing and backfilling rather than with a maintenance window. Admitting the key was the mistake scores better than pretending the strategy was fine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sharding also creates costs that did not exist on one machine, and a strong answer volunteers them before being asked. &lt;strong&gt;Cross-shard joins&lt;/strong&gt; and &lt;strong&gt;cross-shard transactions&lt;/strong&gt; are the big ones. A query that spans shards becomes a &lt;strong&gt;scatter-gather&lt;/strong&gt; — fan the request to every shard, then merge the results in the application — which is slower and harder to paginate and rank. A transaction that must update rows on two shards can no longer rely on the database's single-node ACID guarantee; you reach for &lt;strong&gt;two-phase commit&lt;/strong&gt; (correct but slow and prone to blocking) or a &lt;strong&gt;saga&lt;/strong&gt; (a sequence of local transactions with compensating undo steps). The standard advice, and the thing to say, is: &lt;em&gt;design the shard key so the common path stays single-shard, and avoid cross-shard transactions wherever you can.&lt;/em&gt; Finally, there is &lt;strong&gt;resharding&lt;/strong&gt; — changing the number of shards or the key when you outgrow the layout — which is painful enough that it is the reason to choose a scheme (consistent hashing, a generous number of virtual shards up front) that minimises how much data moves when you grow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb769qvyol370fkt9vz95.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb769qvyol370fkt9vz95.jpeg" alt="A diagram showing the shard-key decision: a good shard key with high cardinality spreading load evenly across shards versus a celebrity or hotspot key concentrating most traffic on one shard, and the cost sharding creates where a query or transaction that spans shards must scatter-gather across all shards or run a two-phase commit." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Choose a shard key and survive the hotspot follow-up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Sharding in a system design interview
&lt;/h2&gt;

&lt;p&gt;The thing that separates a clean answer from a muddled one is &lt;em&gt;timing&lt;/em&gt;. Do not reach for sharding first. The ordered path an interviewer wants to hear is: a single primary, then &lt;strong&gt;read replicas&lt;/strong&gt; when reads dominate, then &lt;strong&gt;caching&lt;/strong&gt; to absorb the hot reads, and only when &lt;strong&gt;writes or storage&lt;/strong&gt; still exceed one machine do you shard. Introducing sharding at that moment — and saying explicitly "replicas and cache won't help here because the bottleneck is write throughput, so I'll shard" — is the signal that you are reasoning, not reciting. Then name the key, justify it against the access pattern in one sentence, call out the hotspot risk, and state how you will handle the queries that have to cross shards.&lt;/p&gt;

&lt;p&gt;Scan the probes that follow, because they rhyme across every round:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"When do you shard instead of adding replicas?"&lt;/strong&gt; → When the bottleneck is write throughput or storage, not reads. Replicas copy the whole dataset and apply every write, so they scale reads only; sharding is the only thing that splits writes and storage across machines. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Range, hash or directory — which and why?"&lt;/strong&gt; → Range when range scans dominate and you can manage the sequential-write hotspot; hash (with consistent hashing) when you need even load and can live without cheap range scans; directory when you need flexible rebalancing and can keep the lookup table fast and highly available. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What makes a good shard key?"&lt;/strong&gt; → High cardinality, even access distribution, and alignment with the dominant query so the common path stays single-shard. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"One shard is a hotspot — what happened and how do you fix it?"&lt;/strong&gt; → The key concentrated load; fix with a compound key, splitting the hot entity, or caching in front, and rebalance online. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A query has to span shards — how?"&lt;/strong&gt; → Scatter-gather and merge in the application for reads; for writes across shards, avoid it, or use two-phase commit or a saga, accepting the latency and complexity cost. (technical execution)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each maps to a debrief dimension. Reading the list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a short plan
&lt;/h2&gt;

&lt;p&gt;You do not need to memorise a database internals textbook. You need these ideas automatic so the next "design X" round, when it reaches the single-database wall, is just the same reasoning with new nouns.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 1:&lt;/strong&gt; Lock the vocabulary — vertical vs horizontal partitioning, and why sharding (horizontal across machines) is the only thing that scales writes and storage. Say the replicas-vs-sharding sentence until it is reflex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Drill the three strategies out loud — range, hash (with consistent hashing), directory — and when you would pick each, in one sentence apiece.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; Own the shard key — the three properties, the hotspot/celebrity problem and its fixes, and the cross-shard join/transaction and resharding costs. Practise volunteering the costs before you are asked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 4:&lt;/strong&gt; A timed HLD round where you reach the sharding moment and defend a shard key against follow-ups, narrating the whole way, with someone (or something) pushing back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explainer backs one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;system design interview&lt;/a&gt; prep map — once sharding is automatic, the same reasoning carries the key-value store, the CDN and the analytics rounds that all lean on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the difference between sharding and partitioning?
&lt;/h3&gt;

&lt;p&gt;Partitioning is the general idea of splitting one dataset into smaller parts; sharding is a specific kind of partitioning. Partitioning comes in two forms: vertical, which splits a table by column or feature (profile fields in one store, billing in another), and horizontal, which splits a table by row into subsets that share the same schema. Sharding is horizontal partitioning carried across separate machines — each shard is its own database on its own hardware holding a slice of the rows. The practical point is that logical partitioning on one server does not raise a capacity ceiling, whereas sharding across machines adds CPU, memory, disk and connections, which is why sharding is what scales writes and storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a shard key and why does it matter so much?
&lt;/h3&gt;

&lt;p&gt;A shard key is the column or combination of columns whose value decides which shard a row belongs to. It matters more than the sharding strategy because it determines how evenly load spreads and whether your common queries stay on one shard. A good shard key has high cardinality (many distinct values so data divides finely), even access distribution (no value attracts far more traffic than others), and alignment with the dominant query (most reads and writes answerable from a single shard). A poor key concentrates traffic on one shard or forces most queries to fan out across all of them, and because changing the key later means resharding, it is a decision you want to get right up front.&lt;/p&gt;

&lt;h3&gt;
  
  
  Range-based vs hash-based sharding — when do I use each?
&lt;/h3&gt;

&lt;p&gt;Use range-based sharding when range scans dominate your workload, because a contiguous range of keys lives on a single shard and those queries stay local; the risk is that monotonically increasing keys like timestamps or auto-increment ids send every new write to the newest shard and create a hotspot. Use hash-based sharding when you need even load distribution and can live without cheap range scans, because hashing scatters adjacent keys across all shards; pair it with consistent hashing so that adding or removing a shard moves only a small fraction of keys instead of remapping everything, which naive &lt;code&gt;hash mod N&lt;/code&gt; would do.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the hotspot or celebrity problem in sharding?
&lt;/h3&gt;

&lt;p&gt;A hotspot is a single shard that receives far more traffic than the others, usually because the shard key concentrates load. The classic case is the celebrity problem: shard a social platform by user and most users are evenly spread, but one account with tens of millions of followers overwhelms its shard while the rest idle. The same happens with low-cardinality keys or sequential keys under range sharding. Fixes include a compound shard key that combines the hot dimension with another (such as user plus a time bucket), deliberately splitting the hot entity's data across shards, and placing a cache in front of the hot key so most reads never reach the shard.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do queries work when the data spans multiple shards?
&lt;/h3&gt;

&lt;p&gt;A read that spans shards becomes a scatter-gather: the application fans the request out to every relevant shard, each returns its local result, and the application merges, sorts and paginates them — slower and more complex than a single-shard query, which is why you try to design the shard key so the common path stays on one shard. A write or transaction that spans shards can no longer rely on a single database's ACID guarantee, so you either avoid it, use two-phase commit (correct but slow and prone to blocking), or use a saga, which is a sequence of local transactions with compensating actions to undo on failure. The general rule is to keep the frequent path single-shard and treat cross-shard operations as the expensive exception.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise sharding questions out loud?
&lt;/h3&gt;

&lt;p&gt;Reading about sharding is not the same as defending a shard key while someone asks why one shard is on fire. On Zynter, the high-level design track runs live voice rounds modelled on how real system design loops run — you draw the architecture on a shared canvas, reach the single-database wall, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what a shard key is. The round is won on defending the one you chose out loud.&lt;/h3&gt;

&lt;p&gt;Pick a high-level design round, scale it past the single-database wall on the canvas, and defend your shard key to a voice interviewer that keeps asking "on what key, and why?" and "one shard is 5x hotter than the rest — what went wrong?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise an HLD round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>walkin</category>
      <category>designsystem</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>CAP theorem explained: the system design interview concept, done precisely</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 15:47:57 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/cap-theorem-explained-the-system-design-interview-concept-done-precisely-3ko4</link>
      <guid>https://dev.to/gowthampotureddi/cap-theorem-explained-the-system-design-interview-concept-done-precisely-3ko4</guid>
      <description>&lt;h2&gt;
  
  
  CAP theorem explained: the system design interview concept, done precisely
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;CAP theorem&lt;/strong&gt; is the single most quoted — and most misquoted — idea in a &lt;strong&gt;system design interview&lt;/strong&gt;. State it as "pick two of consistency, availability and partition tolerance" and a good interviewer will immediately push back, because that framing is wrong in a way that decides whether you actually understand &lt;strong&gt;distributed systems&lt;/strong&gt; or just memorised a triangle. CAP is not a menu of three you choose two from; it is a statement about what happens to a replicated datastore at the one moment its network breaks, and the whole value of the idea is the choice it forces on you at that moment.&lt;/p&gt;

&lt;p&gt;Here is the version that survives follow-ups. In any system that keeps copies of data on more than one machine, the network &lt;em&gt;will&lt;/em&gt; eventually drop or delay messages between those machines — a &lt;strong&gt;partition&lt;/strong&gt;. You do not get to opt out of that; partitions happen whether you planned for them or not. So the real question CAP answers is: &lt;em&gt;when a partition is happening, and a request arrives at a node that cannot reach the others, do you answer it anyway with possibly-stale data, or do you refuse to answer until you can guarantee it is correct?&lt;/em&gt; Answer anyway and you chose &lt;strong&gt;availability&lt;/strong&gt;; refuse until correct and you chose &lt;strong&gt;consistency&lt;/strong&gt;. That is the entire theorem, and reaching it in one breath — partitions are not optional, so the choice is consistency &lt;em&gt;versus&lt;/em&gt; availability during a partition — is what the round is testing. This guide walks the three properties precisely, the trade-off they really force, how real databases land on each side, and how to use CAP to justify a design decision out loud instead of reciting it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F44ydsz0602yz2kldh4ih.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F44ydsz0602yz2kldh4ih.jpeg" alt="Title card for a system design interview explainer on the CAP theorem, showing three labelled pillars — Consistency, Availability and Partition tolerance — with a lightning bolt splitting a cluster of database nodes into two groups and a single highlighted decision between answering with stale data or refusing the request, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the CAP theorem comes up in system design interviews
&lt;/h2&gt;

&lt;p&gt;CAP surfaces the moment your design has more than one copy of the data — which is almost always. Add a read replica, shard a database, put a cache in front of it, replicate across regions, and you have a distributed datastore whose nodes can lose contact with each other. So any time you say "I'll replicate this for availability" or "I'll use Cassandra here" or "the write goes to the primary and fans out to the followers," you have made a CAP decision, and the interviewer will ask you to defend it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your database cluster is replicated across two data centres, and the link between them goes down for thirty seconds. A write request lands on the side that can't reach the other — do you accept the write and answer, or reject it until the link is back? Pick one, and tell me what the user sees either way."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question is the whole point. It cannot be answered by naming the theorem; it is answered by knowing that you must choose, picking a side &lt;em&gt;for this specific data&lt;/em&gt;, and naming the cost the user pays. The system design rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall — what matters for prep is the shape: a decision under a partition, and a follow-up that makes you own the consequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the interviewer is really testing
&lt;/h3&gt;

&lt;p&gt;The score is not whether you can recite "consistency, availability, partition tolerance." A good debrief reads on four dimensions, and CAP touches each: &lt;strong&gt;problem solving&lt;/strong&gt; (did you recognise that partition tolerance is forced, so the real axis is C versus A), &lt;strong&gt;communication&lt;/strong&gt; (can you say in one sentence why this data tolerates staleness, or why it cannot), &lt;strong&gt;technical execution&lt;/strong&gt; (can you map the choice onto a real datastore and its replication/quorum behaviour), and &lt;strong&gt;time management&lt;/strong&gt; (did you make the call quickly and move on, instead of drawing the triangle for five minutes). Reading the theorem teaches you the words; it does not teach you to make the call out loud while someone presses on the consequence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three properties, precisely
&lt;/h2&gt;

&lt;p&gt;Most wrong answers start with a loose definition, so pin each term down — the precision is where the points are.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;What it actually means&lt;/th&gt;
&lt;th&gt;Common misreading&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency (C)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every read returns the most recent write (or an error). Every node shows the same data at the same logical instant — &lt;strong&gt;linearizability&lt;/strong&gt;, a single up-to-date copy as far as any client can tell.&lt;/td&gt;
&lt;td&gt;It is &lt;strong&gt;not&lt;/strong&gt; the "C" in ACID (which is about database invariants and constraints). CAP's C is about reads agreeing across replicas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Availability (A)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every request to a non-failing node receives a non-error response — the system always answers, though the answer may be stale.&lt;/td&gt;
&lt;td&gt;Not "the system is up most of the time." It is a strict guarantee: &lt;em&gt;every&lt;/em&gt; request gets an answer, even during a partition.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partition tolerance (P)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The system keeps operating even when the network drops or arbitrarily delays messages between nodes.&lt;/td&gt;
&lt;td&gt;Not a feature you can bolt on or leave off — in any multi-node system, partitions are a fact of the physical world.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The definition that earns the point is consistency: CAP consistency is &lt;strong&gt;linearizability&lt;/strong&gt;, not the ACID "C." Say that out loud and the interviewer knows you have read past the triangle. The other subtlety is availability — it is an absolute: a system is available under CAP only if it answers &lt;em&gt;every&lt;/em&gt; request even while partitioned, which is a much stronger claim than "high uptime."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6882tjbtzaiihmm6zvxw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6882tjbtzaiihmm6zvxw.jpeg" alt="A diagram defining the three CAP properties and the partition scenario, showing Consistency as two replicas returning the identical latest value to a reader, Availability as a node always returning some answer, and Partition tolerance as a lightning bolt cutting the message link between two groups of nodes so neither side can talk to the other." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The real trade-off: CP vs AP during a partition
&lt;/h2&gt;

&lt;p&gt;Now the correction that the whole round turns on. The "pick two of three" phrasing implies three symmetric options — CA, CP, AP — as if you freely choose any pair. You do not, because &lt;strong&gt;partition tolerance is not negotiable&lt;/strong&gt;. Networks partition: cables are cut, switches fail, a data-centre link saturates, a GC pause makes a node look dead. If your data lives on more than one machine, partitions &lt;em&gt;will&lt;/em&gt; happen, so &lt;strong&gt;P is mandatory&lt;/strong&gt;. That collapses the three choices into one real axis.&lt;/p&gt;

&lt;p&gt;So the honest statement of CAP is this: &lt;strong&gt;when a partition occurs, you must sacrifice either consistency or availability&lt;/strong&gt; — you cannot keep both. Consider the request that lands on a node cut off from its peers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CP (consistency over availability):&lt;/strong&gt; the node refuses to answer — it returns an error or blocks — because it cannot guarantee it has the latest write. Data stays correct everywhere; some requests fail during the partition. You chose consistency and gave up availability on the minority side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AP (availability over consistency):&lt;/strong&gt; the node answers anyway from its local copy, which may be stale, and the replicas reconcile later (eventual consistency). Every request gets an answer; some answers are briefly wrong. You chose availability and gave up strong consistency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"CA" is the one that does not really exist&lt;/strong&gt; in a distributed system. A CA system is one that assumes partitions never happen — which is only true of a single node (no network between replicas to partition). The moment you have two nodes, the network can split them, and you are forced back onto the CP-versus-AP axis. So in practice CAP is a binary choice made &lt;em&gt;per partition&lt;/em&gt;: refuse and stay correct, or answer and risk staleness.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"During the partition, the CP system makes the user see an error on the cut-off side; the AP system makes the user see slightly old data. Which failure is acceptable for &lt;em&gt;this&lt;/em&gt; feature? A bank balance transfer wants CP — better to fail the write than double-spend. A social feed's like count wants AP — better to show a stale count than a spinner."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7r06b5ys3fiapk9srace.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7r06b5ys3fiapk9srace.jpeg" alt="A decision diagram for CP versus AP during a network partition, showing a write arriving at a node isolated from the rest of the cluster, with one branch labelled CP where the node rejects the request to preserve a single correct value, and another branch labelled AP where the node accepts the request from its local replica and the copies reconcile after the partition heals, and a struck-through CA box noting it only holds on a single node." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend a CP-vs-AP choice out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  CP vs AP in practice, and why PACELC completes the picture
&lt;/h2&gt;

&lt;p&gt;Naming real systems shows you have used this, not just read it — but say the caveat too, because most "AP" and "CP" databases are tunable, so the label is a default, not a law.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Leaning&lt;/th&gt;
&lt;th&gt;Example systems&lt;/th&gt;
&lt;th&gt;Default behaviour during a partition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;CP&lt;/strong&gt; (consistency first)&lt;/td&gt;
&lt;td&gt;HBase, MongoDB (default/majority), etcd, ZooKeeper, Spanner&lt;/td&gt;
&lt;td&gt;The minority/unreachable side stops serving affected requests; a leader or quorum must be reachable before a write or consistent read succeeds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;AP&lt;/strong&gt; (availability first)&lt;/td&gt;
&lt;td&gt;Cassandra, DynamoDB, Riak, Couchbase&lt;/td&gt;
&lt;td&gt;Every reachable node keeps answering from its replica; writes are accepted on both sides and reconciled afterwards (eventual consistency, last-write-wins or CRDT-style merges).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Tunable&lt;/strong&gt; (per request)&lt;/td&gt;
&lt;td&gt;Cassandra / DynamoDB quorum levels&lt;/td&gt;
&lt;td&gt;Consistency is a knob: require more replicas to agree (towards CP) or fewer (towards AP) per read/write. &lt;code&gt;R + W &amp;gt; N&lt;/code&gt; buys you read-your-writes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest add-on that impresses: &lt;strong&gt;CAP only describes the partition case, and partitions are rare&lt;/strong&gt;. What about the 99.9% of the time the network is fine? That is what &lt;strong&gt;PACELC&lt;/strong&gt; captures: &lt;em&gt;if Partition (P), choose Availability or Consistency (A/C); Else (E), choose Latency or Consistency (L/C)&lt;/em&gt;. In other words, even with a healthy network there is a standing trade-off — synchronous replication for strong consistency costs latency, asynchronous replication for low latency costs consistency. So a full answer sounds like: "This is PC/EL — consistent when partitioned, but tuned for low latency the rest of the time." PACELC is the model that explains why DynamoDB feels fast (EL) and Spanner feels strict (EC) even when nothing is broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvciajl903wu5o70rjeer.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvciajl903wu5o70rjeer.jpeg" alt="A diagram classifying real databases as CP-leaning or AP-leaning with a tunable-quorum middle, and a PACELC summary showing that if there is a partition you choose availability or consistency, else you choose latency or consistency, with DynamoDB labelled PA-EL and Spanner labelled PC-EC." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Classify a datastore as CP or AP and justify it&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  How to use the CAP theorem in a system design interview
&lt;/h2&gt;

&lt;p&gt;The mistake is reciting CAP as trivia. The move that scores is wielding it as a decision tool: when you pick a datastore or a replication scheme, say which side of the partition line it sits on and why &lt;em&gt;this data&lt;/em&gt; can live with that. Master these five and you are ready for any version of the follow-up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Can't you just have all three?"&lt;/strong&gt; → No. Partitions are a fact of networks, so P is mandatory in any multi-node system; that forces a choice between C and A &lt;em&gt;during a partition&lt;/em&gt;. "CA" only describes a single node that can't be partitioned. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"CP or AP for this feature — which and why?"&lt;/strong&gt; → Tie it to the data: strong-consistency-critical state (payments, inventory, unique usernames) is CP — fail rather than serve wrong; staleness-tolerant state (feeds, counts, product catalogues) is AP — answer rather than stall. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What does the user actually see during the partition?"&lt;/strong&gt; → With CP, errors or timeouts on the cut-off side until it heals; with AP, slightly stale reads and conflicts that reconcile afterwards. Name the concrete symptom, not just the label. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Which real database, and is it really always that?"&lt;/strong&gt; → Name one (Cassandra = AP, HBase/ZooKeeper = CP) and immediately add that many are tunable per request via quorum (&lt;code&gt;R + W &amp;gt; N&lt;/code&gt;), so the label is a default. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What about when the network is fine?"&lt;/strong&gt; → Reach for PACELC: even without a partition there's a latency-versus-consistency choice, so describe the system as PC/EL or PA/EC to show you think beyond the failure case. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone keeps asking "but what does the &lt;em&gt;user&lt;/em&gt; see?" — that only comes from saying them out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a short plan
&lt;/h2&gt;

&lt;p&gt;You do not need to memorise a taxonomy. You need the reframe automatic and one or two real systems in each camp so the choice is reflex.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Day 1:&lt;/strong&gt; Learn the three definitions precisely — especially that CAP consistency is linearizability, not ACID's C, and that availability is an absolute per-request guarantee. Write each in one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 2:&lt;/strong&gt; Internalise the reframe: P is mandatory, so the real choice is C versus A during a partition, and CA is a single-node fantasy. Be able to say the whole theorem in two sentences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 3:&lt;/strong&gt; Map real stores — Cassandra/DynamoDB (AP, tunable), HBase/ZooKeeper/etcd (CP) — and learn PACELC so you can describe a system when the network is healthy too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 4:&lt;/strong&gt; Drill the application: for ten features (bank transfer, like count, shopping cart, username registration, DNS, chat delivery), say CP or AP and why in one sentence, out loud, then answer "what does the user see during a partition?" each time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the CAP theorem in simple terms?
&lt;/h3&gt;

&lt;p&gt;The CAP theorem says that a system which stores data on more than one machine cannot simultaneously guarantee all three of consistency (every read sees the latest write), availability (every request gets a non-error response) and partition tolerance (the system keeps working when the network between nodes fails). In practice, because networks do fail, partition tolerance is not optional — so when a partition happens you must choose between staying consistent (refuse requests you can't answer correctly) and staying available (answer anyway with possibly-stale data). It is a statement about the trade-off forced on a replicated datastore the moment its nodes can't talk to each other.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why can't a distributed system have all three of C, A and P?
&lt;/h3&gt;

&lt;p&gt;Because during a network partition, consistency and availability directly conflict. Imagine a node cut off from the rest of the cluster receives a request. To stay &lt;em&gt;consistent&lt;/em&gt; it must refuse, since it cannot confirm it has the latest data — but refusing breaks &lt;em&gt;availability&lt;/em&gt;. To stay &lt;em&gt;available&lt;/em&gt; it must answer from its local copy — but that copy may be stale, which breaks &lt;em&gt;consistency&lt;/em&gt;. You cannot do both at once on that node, and since partitions are unavoidable in a multi-machine system (so P is mandatory), you are always forced to sacrifice either C or A when one occurs.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between CP and AP systems?
&lt;/h3&gt;

&lt;p&gt;A CP system prioritises consistency: during a partition it stops serving requests it cannot guarantee are correct — the unreachable side returns errors or blocks — so data never diverges, at the cost of some requests failing. HBase, ZooKeeper, etcd and Spanner lean CP. An AP system prioritises availability: during a partition every reachable node keeps answering from its own replica, so no request fails, but different nodes may briefly return different values that reconcile afterwards (eventual consistency). Cassandra, DynamoDB and Riak lean AP. Many of these are tunable per request through quorum settings, so the label is a default rather than a hard rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a CA system possible?
&lt;/h3&gt;

&lt;p&gt;Not in a genuinely distributed setting. "CA" means a system that is consistent and available but gives up partition tolerance — which only holds if partitions can never happen, and the only way that is true is a single node with no network between replicas to split. A traditional single-server relational database is sometimes called CA, but the instant you replicate it across machines, the network can partition them and you are back to choosing CP or AP. That is why experienced engineers treat CAP as a binary CP-versus-AP choice rather than a menu of three.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is PACELC and how does it extend CAP?
&lt;/h3&gt;

&lt;p&gt;PACELC fills the gap CAP leaves: CAP only tells you what happens during a partition, which is rare, and says nothing about normal operation. PACELC reads "if Partition, choose Availability or Consistency; Else, choose Latency or Consistency." The "else" branch captures the everyday trade-off — replicating synchronously for strong consistency adds latency, while replicating asynchronously for low latency weakens consistency, even when nothing is broken. So a system is described with both halves: DynamoDB is PA/EL (available when partitioned, low-latency otherwise), while a strict store like Spanner is PC/EC. It is the more complete model to reach for after you've named CAP.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise using the CAP theorem out loud?
&lt;/h3&gt;

&lt;p&gt;Reading the theorem is not the same as making the call while someone presses on the consequence. On Zynter, the high-level design track runs live voice rounds modelled on how real system design loops run — you talk through a design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time ("the link drops — do you accept the write or reject it? what does the user see?"), then gives you a debrief on problem solving, communication, technical execution and time management. The score is a practice signal, not a hiring decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can recite CAP. The round is won by making the CP-or-AP call out loud and owning what the user sees.&lt;/h3&gt;

&lt;p&gt;Pick a high-level design round, reach for the CAP decision when you choose a datastore, and defend it to a voice interviewer that keeps asking "the network splits — do you answer or refuse?" and "what does the user see during the partition?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise an HLD round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>dataengineering</category>
      <category>walkin</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Consistent hashing explained: the trick that keeps a cluster calm when a node joins or dies</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:31:13 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/consistent-hashing-explained-the-trick-that-keeps-a-cluster-calm-when-a-node-joins-or-dies-3bb9</link>
      <guid>https://dev.to/gowthampotureddi/consistent-hashing-explained-the-trick-that-keeps-a-cluster-calm-when-a-node-joins-or-dies-3bb9</guid>
      <description>&lt;h2&gt;
  
  
  Consistent hashing explained: the trick that keeps a cluster calm when a node joins or dies
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Consistent hashing&lt;/strong&gt; is one of those &lt;strong&gt;system design interview&lt;/strong&gt; ideas that sounds like trivia until the follow-up lands: you have a fleet of cache servers or a sharded database, you add one machine, and suddenly the whole thing falls over. The reason is the naive way of spreading keys across servers — &lt;code&gt;hash(key) % N&lt;/code&gt; — and the reason consistent hashing exists is to stop exactly that collapse. If you have ever drawn "shard this across N nodes" on a whiteboard, you have already walked up to the edge of this concept; the question is whether you can explain what happens at the edge.&lt;/p&gt;

&lt;p&gt;The one-sentence version, the one worth being able to say out loud in any &lt;strong&gt;distributed systems&lt;/strong&gt; round: with plain modulo hashing, changing the number of servers remaps &lt;strong&gt;almost every key at once&lt;/strong&gt;, but consistent hashing places servers and keys on a ring so that adding or removing a node only moves about &lt;strong&gt;K/N&lt;/strong&gt; of the keys — and virtual nodes keep that movement spread evenly across the cluster. This explainer builds that sentence up from nothing: first why modulo hashing breaks, then the ring that fixes it, then the virtual nodes that make the ring usable in production — and finally where it shows up in the real rounds, and how to narrate it when someone is pushing back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnicbk9kg2z1vpojksvhw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnicbk9kg2z1vpojksvhw.jpeg" alt="Title card for a system design interview explainer on consistent hashing, showing keys mapped around a circular hash ring to the servers placed on it, with a note that adding or removing one server moves only a small arc of keys instead of remapping everything, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why consistent hashing comes up in system design interviews
&lt;/h2&gt;

&lt;p&gt;Consistent hashing is rarely the question on its own — it is the follow-up hiding inside a bigger one. The moment your design says "spread the data across N nodes" or "route each key to one of N cache servers" — a distributed cache, a sharded key-value store, a CDN choosing an edge, a message queue partitioning by key — the interviewer has a lever to pull: &lt;em&gt;what happens to that mapping when N changes?&lt;/em&gt; Nodes are not static. They die, you add capacity for a traffic spike, you take one down to patch it. If your routing scheme reshuffles everything each time, your cache goes cold and your storage layer melts under a mass data migration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You have a 10-server cache fleet and you add one more to handle load. With a simple hash-and-modulo scheme, what fraction of your keys now map to a different server — and why should that terrify you?" The answer is roughly 10 out of 11 keys move, almost all of them, so almost every request becomes a cache miss at the same instant and stampedes your origin. That is the failure consistent hashing is designed to prevent, and naming it is the point of the question.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These rounds are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt; — the way a real interviewer probes a design — not scraped from anyone's recall. What matters for prep is the pattern: the concept is easy to define and hard to defend, and the defence is where the points are.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the interviewer is really testing
&lt;/h3&gt;

&lt;p&gt;Naming "consistent hashing" earns you nothing; explaining it earns you the round. The probe reads on three of the four debrief dimensions. &lt;strong&gt;Problem solving:&lt;/strong&gt; do you reach for it at the right moment — the instant data or traffic must spread across a &lt;em&gt;changing&lt;/em&gt; set of nodes — and can you say &lt;em&gt;why&lt;/em&gt; modulo fails rather than just that it does? &lt;strong&gt;Communication:&lt;/strong&gt; can you compress it to one sentence — a membership change moves ~K/N keys, not all of them, and virtual nodes keep it balanced — instead of hand-waving at "a ring"? &lt;strong&gt;Technical execution:&lt;/strong&gt; do you actually know the mechanics — how keys and servers map onto the ring, who owns a key, how many keys move, and what virtual nodes buy you? Reading the definition gets you recognition; only saying it out loud under follow-ups gets you fluency.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: why modulo hashing breaks
&lt;/h2&gt;

&lt;p&gt;Start where every candidate starts, because the interviewer wants to hear you reject it for the right reason. To spread K keys across N servers, the obvious scheme is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;server_index = hash(key) % N&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;While N is fixed, this is great — a good hash function spreads keys evenly, each server gets roughly K/N of them, and lookup is a single modulo. The whole scheme rests on one brittle assumption: &lt;strong&gt;N never changes.&lt;/strong&gt; The day it does, the &lt;code&gt;% N&lt;/code&gt; term changes for &lt;em&gt;every&lt;/em&gt; key at once, and almost every key is now assigned to a different server.&lt;/p&gt;

&lt;p&gt;A small, concrete illustration — the kind you would sketch on the board. Take keys whose hashes are the numbers 10 through 17, and start with &lt;strong&gt;N = 4&lt;/strong&gt; servers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key hash&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;% 4&lt;/code&gt; → server&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;% 5&lt;/code&gt; → server (after adding one)&lt;/th&gt;
&lt;th&gt;Moved?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;moved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adding a single server moved &lt;strong&gt;all eight&lt;/strong&gt; keys. That is not a quirk of this example — with modulo hashing, a change of one in N remaps on the order of &lt;em&gt;all&lt;/em&gt; the keys, because the divisor itself changed. Now picture the two places this scheme actually lives. In a &lt;strong&gt;distributed cache&lt;/strong&gt;, every moved key is suddenly a miss, so the instant you add or lose a node, almost the entire fleet misses at once and every request falls through to the origin database — a &lt;strong&gt;cache stampede&lt;/strong&gt; that can take the origin down. In a &lt;strong&gt;sharded store&lt;/strong&gt;, "the key moved" means the &lt;em&gt;data&lt;/em&gt; has to physically move to its new shard, so a one-node change triggers a migration of almost the whole dataset. Either way, the thing you reach for next has to satisfy one property: &lt;strong&gt;when the node set changes, as few keys as possible should move.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m7p9akimcpxvfpvx1a8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6m7p9akimcpxvfpvx1a8.jpeg" alt="A diagram showing that with hash(key) modulo N, going from 4 servers to 5 servers forces almost every key to a different server: a row of keys mapped to servers 0 to 3 on top, the same keys mapped to servers 0 to 4 below, with most of the mappings changed and highlighted, labelled as a cache stampede because every moved key is now a miss." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: the hash ring
&lt;/h2&gt;

&lt;p&gt;Consistent hashing changes one thing — it stops tying a key's owner to the &lt;em&gt;count&lt;/em&gt; of servers. Instead of mapping keys into &lt;code&gt;N&lt;/code&gt; buckets, map both keys and servers into the same large, fixed &lt;strong&gt;circular keyspace&lt;/strong&gt; — think of the output range of the hash function, &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;2^32 − 1&lt;/code&gt;, bent into a ring so the largest value wraps back to zero.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Place the servers.&lt;/strong&gt; Hash each server (by its name or IP) to a point on the ring. The number of servers no longer appears in the key's formula at all — servers are just points on a fixed circle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Place the keys.&lt;/strong&gt; Hash each key to a point on the same ring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assign ownership.&lt;/strong&gt; A key is owned by the &lt;strong&gt;first server you meet going clockwise&lt;/strong&gt; from the key's point. That server, and only that server, is responsible for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payoff is entirely in what happens on a membership change. &lt;strong&gt;Add a server:&lt;/strong&gt; it lands at one point on the ring and takes over only the keys in the arc between it and the &lt;em&gt;previous&lt;/em&gt; server clockwise — the keys that used to belong to its clockwise neighbour. Every other key on the ring is untouched. &lt;strong&gt;Remove a server&lt;/strong&gt; (it crashed): only the keys it owned move, and they all go to the next server clockwise. Nothing else moves.&lt;/p&gt;

&lt;p&gt;So instead of remapping almost everything, a one-node change moves, on average, the keys in a single arc — roughly &lt;strong&gt;K/N keys&lt;/strong&gt; (K keys spread over N servers). That is the whole idea, and it is the sentence to say out loud: &lt;em&gt;keys and servers live on the same ring, a key belongs to the next server clockwise, so adding or removing a node only disturbs one arc — about K/N keys — while the rest of the cluster never notices.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwte8wt2b6fvacbj7ddkw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwte8wt2b6fvacbj7ddkw.jpeg" alt="A hash ring diagram: a circle representing the keyspace from 0 to 2 to the 32 minus 1, with four servers hashed to points on the ring and several keys hashed to other points, each key assigned to the first server clockwise; a new server is added on one arc and only the keys in that arc are reassigned, with the rest untouched." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual nodes: keeping the ring balanced
&lt;/h2&gt;

&lt;p&gt;The plain ring has a weakness, and a good interviewer will find it, so raise it yourself. With only a handful of servers hashed onto the ring, the points land wherever the hash function throws them — and random points on a circle are &lt;strong&gt;lumpy&lt;/strong&gt;, not evenly spaced. Two problems follow. First, &lt;strong&gt;unbalanced load:&lt;/strong&gt; if one server happens to sit just after a long empty arc, it owns a disproportionate share of the keyspace and runs hot while others idle. Second, &lt;strong&gt;a big blast radius on failure:&lt;/strong&gt; when a server dies, its &lt;em&gt;entire&lt;/em&gt; arc dumps onto the single next server clockwise — so one node's death can double the load on one unlucky neighbour, which may then fall over in turn.&lt;/p&gt;

&lt;p&gt;The fix is &lt;strong&gt;virtual nodes&lt;/strong&gt; (also called vnodes or tokens). Instead of hashing each physical server to one point, hash it to &lt;strong&gt;many&lt;/strong&gt; points — dozens or hundreds — scattered around the ring, each a "virtual" copy of that server. Two things improve at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Even distribution.&lt;/strong&gt; With many small arcs per server interleaved all around the ring, the law of large numbers smooths the load — the more virtual points per server, the tighter each server's share converges on its fair K/N, and the hot-spot risk shrinks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A small, spread-out blast radius.&lt;/strong&gt; When a physical server dies, each of its virtual points hands its little arc to a &lt;em&gt;different&lt;/em&gt; clockwise neighbour, so the departing load is spread across &lt;strong&gt;all&lt;/strong&gt; the surviving servers instead of crushing one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Virtual nodes also give you a clean way to handle &lt;strong&gt;heterogeneous hardware&lt;/strong&gt;: a server with twice the capacity simply gets twice as many virtual points, so it owns twice the keyspace — weighting falls out of the same mechanism. This is why real systems never ship the naive one-point-per-node ring; vnodes are what make consistent hashing usable in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your ring is live and one node is taking nearly double the traffic of the others. What went wrong, and how do you fix it without re-sharding the world?" The cause is a lumpy ring — too few points per server, so that node owns an oversized arc. The fix is virtual nodes: give every physical server many points on the ring so the arcs even out and load converges on its fair share, and a future node failure spreads across all survivors instead of one. No mass remap required — that is the point of the ring.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Explain the ring and virtual nodes out loud, under follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8hmf03yxo251f1od51bf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8hmf03yxo251f1od51bf.jpeg" alt="A two-part diagram: on the left, a ring with only three physical servers producing uneven arcs and an unbalanced load, versus a ring where each server has many virtual points scattered around so the arcs even out and a departing server's keys spread across all the others; on the right, a short list of where consistent hashing is used: Dynamo and Cassandra partitioning, distributed caches, CDN edge selection, load balancers." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where consistent hashing shows up in real systems
&lt;/h2&gt;

&lt;p&gt;The reason this concept is worth owning is that it is load-bearing in half the HLD rounds you will draw. When you reach it in a design, name the system it belongs to — that is what turns "I know the ring" into "I know where the ring goes."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dynamo and Cassandra partitioning.&lt;/strong&gt; Both spread data across the cluster with a &lt;strong&gt;token ring&lt;/strong&gt; — consistent hashing with many tokens (virtual nodes) per server. A node joining or leaving the cluster only reshuffles its neighbours' ranges, which is what lets these stores scale and heal without a global migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed caches.&lt;/strong&gt; A memcached or Redis cache fleet sharded client-side uses a ring so that losing a cache node only cools one arc of keys, not the whole cache — exactly the stampede the modulo scheme caused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDNs.&lt;/strong&gt; Edge-server and origin-shield selection can use consistent hashing so that a given object consistently lands on the same edge cache, keeping hit rates high as the edge fleet changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancers and sharded-database routing.&lt;/strong&gt; Any routing layer that must send "the same key to the same node" while the node set changes under it reaches for the ring — session or shard affinity being the common case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The common thread, and the line that ties it together: consistent hashing is the answer whenever you need to &lt;strong&gt;route a key to a node such that changing the node set moves as few keys as possible.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Core concept&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Drop the ring into a Distributed Cache or Key-Value Store round&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  How to talk about consistent hashing in an interview
&lt;/h2&gt;

&lt;p&gt;Scan the probes above and they rhyme. Master these five and you can defend the concept in any round that leans on it, not just recite the definition.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just use &lt;code&gt;hash(key) % N&lt;/code&gt;?"&lt;/strong&gt; → Because the moment N changes, the divisor changes for every key, so almost all keys remap at once — a cold cache that stampedes the origin, or a near-total data migration in a sharded store. The requirement is that a membership change move as few keys as possible. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How does the ring actually assign a key to a server?"&lt;/strong&gt; → Hash both servers and keys onto a fixed circular keyspace; a key is owned by the first server clockwise from it. Adding or removing a node only reassigns the one arc next to it — about K/N keys — leaving the rest untouched. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Add one node to a fleet of N — how many keys move?"&lt;/strong&gt; → Roughly K/N on average: just the keys in the new node's arc, taken from its clockwise neighbour. Compare that with modulo hashing, where nearly all K keys move. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Your load is uneven / one node runs hot — why, and what's the fix?"&lt;/strong&gt; → A lumpy ring from too few points per server. Add virtual nodes: many points per physical server so arcs even out and a failure spreads across all survivors. Weight bigger machines with more vnodes. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Where would you actually use this?"&lt;/strong&gt; → Dynamo/Cassandra token rings, distributed caches, CDN edge selection, sharded-DB and load-balancer routing — anywhere a key must map to a node while the node set changes. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each maps to a debrief dimension. Reading the list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a short plan
&lt;/h2&gt;

&lt;p&gt;You do not need a week for this one — you need it &lt;em&gt;automatic&lt;/em&gt;, so it drops cleanly into the middle of a bigger design without breaking your stride.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Draw the ring from memory.&lt;/strong&gt; Circle, a few servers as points, a few keys, each key's clockwise arrow to its owner. If you can draw it in thirty seconds, you can draw it while talking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive K/N yourself.&lt;/strong&gt; Add a node on your drawing and shade exactly which keys move. Say the number out loud: "about K over N, one arc."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explain virtual nodes in one sentence.&lt;/strong&gt; "Many points per server so load is even and a failure spreads across everyone." Then the weighting corollary for mixed hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Then defend it under pressure.&lt;/strong&gt; Answer the five follow-ups above out loud, with someone (or something) pushing back — that is the rep that turns recognition into fluency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This explainer is the concept behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the ring is automatic, it slots into the Distributed Cache, Key-Value Store, CDN and Message Queue walkthroughs without a second thought.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is consistent hashing, in one sentence?
&lt;/h3&gt;

&lt;p&gt;Consistent hashing is a way to map keys to servers so that adding or removing a server moves only a small fraction of the keys instead of almost all of them. It works by hashing both servers and keys onto the same fixed circular keyspace (a ring) and assigning each key to the first server clockwise from it. Because a key's owner depends on its position on the ring rather than on the total number of servers, a membership change only reassigns the keys in one arc — on average about K/N of them, where K is the number of keys and N the number of servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not just use &lt;code&gt;hash(key) % N&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;Because &lt;code&gt;% N&lt;/code&gt; ties every key's assignment to the server count N. A good hash spreads keys evenly while N is fixed, but the instant N changes — you add capacity or a node dies — the modulo result changes for essentially every key, so almost all of them map to a different server at once. For a cache that means a near-total set of simultaneous misses stampeding the origin database; for a sharded store it means migrating nearly all the data. Consistent hashing exists specifically to avoid that mass remap.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are virtual nodes and why do they matter?
&lt;/h3&gt;

&lt;p&gt;Virtual nodes (also called vnodes or tokens) mean hashing each physical server to many points on the ring instead of one. They fix the two weaknesses of a plain ring. First, with only a few points the ring is lumpy, so some servers own oversized arcs and run hot; many points per server smooth the load toward each server's fair share. Second, without vnodes a dying server dumps its whole arc on one neighbour, but with many points its load spreads across all the surviving servers. Virtual nodes also let you weight heterogeneous hardware — a bigger machine simply gets more points.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is consistent hashing actually used?
&lt;/h3&gt;

&lt;p&gt;In data stores that partition across a cluster, such as Amazon Dynamo and Apache Cassandra, which use a token ring (consistent hashing with many tokens per node) to distribute data and to rebalance when nodes join or leave. In distributed caches like memcached or Redis sharded across many servers, so losing one node only cools one arc of keys. In CDNs for edge-server and origin-shield selection, to keep an object on the same cache as the fleet changes. And in load balancers and sharded-database routing layers that must send the same key to the same node while the node set shifts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is consistent hashing the same as sharding?
&lt;/h3&gt;

&lt;p&gt;No — they are related but not the same. Sharding is the broad idea of splitting data across multiple nodes so no single node holds everything. Consistent hashing is one &lt;em&gt;strategy&lt;/em&gt; for deciding which shard a key belongs to — specifically, a hash-based strategy chosen because it keeps key movement small when the number of shards changes. You can shard by range or by a lookup table instead; you reach for consistent hashing when you want even distribution &lt;em&gt;and&lt;/em&gt; minimal reshuffling as nodes come and go.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise explaining consistent hashing out loud?
&lt;/h3&gt;

&lt;p&gt;Reading this page is not the same as defending the ring while someone interrupts with "add a node — how many keys move?" On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you draw on a shared canvas and a voice interviewer pushes with follow-ups one at a time, including the partitioning and rebalancing questions this concept lives inside, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what a hash ring is. The round is won on explaining K/N out loud while someone pushes back.&lt;/h3&gt;

&lt;p&gt;Pick an HLD round that leans on partitioning — the Distributed Cache, Key-Value Store or CDN — draw the ring on the canvas, and defend it out loud to a voice interviewer that keeps asking "add a node — what moves?" and "one node is hot — why?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise an HLD round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>walkin</category>
      <category>dataengineering</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Design Yelp: the proximity-search system design walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:20:30 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-yelp-the-proximity-search-system-design-walkthrough-3lhb</link>
      <guid>https://dev.to/gowthampotureddi/design-yelp-the-proximity-search-system-design-walkthrough-3lhb</guid>
      <description>&lt;h2&gt;
  
  
  Design Yelp: the proximity-search system design walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design Yelp&lt;/strong&gt; sounds like a database with a map on top — a table of businesses, a search box, a list of results — and that picture is exactly what the round is built to puncture. The moment you say "show me the restaurants near me," you have asked a question a normal table cannot answer: &lt;em&gt;find every business within one kilometre of this point&lt;/em&gt;, out of fifty million rows, in a few milliseconds. This is one of the most revealing &lt;strong&gt;proximity search system design&lt;/strong&gt; prompts because it is not really about storing businesses or serving reviews — it is about &lt;strong&gt;geospatial search&lt;/strong&gt;: turning "near me" into something an index can look up without scanning the world.&lt;/p&gt;

&lt;p&gt;The round turns on one structural insight. "Find places near me" is a &lt;strong&gt;geospatial range query&lt;/strong&gt;, not CRUD over a businesses table — and the naive answers both fail in a way the interviewer is waiting for. You cannot scan all fifty million rows and compute a distance for each on every search, and a plain B-tree index on &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; does not save you either: it narrows one dimension but still leaves a long thin strip of the map to scan. The job is to &lt;strong&gt;map two-dimensional proximity onto a one-dimensional, indexable key&lt;/strong&gt; so that "nearby" becomes "shares a prefix" or "lives in the same cell." That has three well-known answers — a &lt;strong&gt;geohash&lt;/strong&gt; (encode the coordinates into a prefix string), a &lt;strong&gt;quadtree&lt;/strong&gt; (recursively subdivide the map until each cell is small), and &lt;strong&gt;Google S2&lt;/strong&gt; (number cells along a space-filling curve) — and naming them, picking one, and defending the choice under uneven density is the spine of the round. Get that reframe — a range query answered by a cell index, then refined with exact distance — and the rest of the &lt;strong&gt;system design interview&lt;/strong&gt; is deciding how a radius becomes a set of cells, how you avoid missing a result sitting just over a boundary, and how the whole thing stays fast when it is read a hundred times for every write. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhw83xw5xi4p9bb77x3ie.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhw83xw5xi4p9bb77x3ie.jpeg" alt="Title card for a system design interview walkthrough on designing Yelp-style proximity search, showing a map pin at the centre of a search radius circle laid over a grid of cells, with only the handful of cells the circle touches lit up and the businesses inside them returned as nearby results, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the proximity-search round is actually run
&lt;/h2&gt;

&lt;p&gt;A proximity-search round runs about 45 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is graded as a &lt;strong&gt;Medium&lt;/strong&gt; system design problem, and that matters for how you spend your time: the win here is not surviving some exotic scale, it is reaching a clean &lt;strong&gt;geospatial index&lt;/strong&gt; quickly and defending the one trade-off that actually bites — what happens when density is wildly uneven. A candidate who nails the index, the query flow and the density story, and leaves the review-and-photos features for later, is doing exactly what the round rewards.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "search the businesses table" stand. "A user is standing downtown and wants every restaurant within one kilometre, ranked by distance — there are fifty million businesses in your system. Walk me through what your service actually does, and don't tell me it scans the table." That question — turning a radius into a bounded lookup — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a Medium-weight problem with one sharp trade-off, and a follow-up after every decision you make.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — number of businesses and search queries per second versus write rate, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: the clients, the search service, the geospatial index, and the business-metadata store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe it as a geospatial range query and reach a cell index, naming geohash, quadtree and S2), &lt;strong&gt;communication&lt;/strong&gt; (did you say in one sentence how each index maps 2D proximity to a 1D key and when you'd pick each), &lt;strong&gt;technical execution&lt;/strong&gt; (covering cells plus their neighbours, an exact haversine filter, ranking and pagination, with the geo index kept separate from the metadata), and &lt;strong&gt;time management&lt;/strong&gt; (did you spend your 45 minutes on the index, the query flow and density, not on the review system). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what a proximity search service must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A user searches for businesses within a radius of a location (or a map viewport) and gets them back ranked; each result shows name, category, rating and distance; a user can open a business and read its details; businesses can be added and their details updated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Filter by category or "open now", sort by rating versus distance, full review read/write, photos, autocomplete on business names — call these out, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Low-latency reads&lt;/strong&gt; — a search returns in milliseconds; the system is &lt;strong&gt;heavily read-dominated&lt;/strong&gt; and business data changes slowly, so eventual consistency on a business edit is fine; high availability; scale to &lt;strong&gt;tens of millions of businesses&lt;/strong&gt; and a high search query rate.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"This is a geospatial range query, not CRUD — I can't scan fifty million rows per search and a plain latitude/longitude index doesn't help, so the core of the design is a spatial index that maps 2D proximity to a 1D key I can look up."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving one thing: &lt;strong&gt;the load is overwhelmingly reads against mostly static data, and the only hard part is the spatial lookup&lt;/strong&gt; — everything else is a cacheable, replicable read store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total businesses&lt;/td&gt;
&lt;td&gt;tens of millions worldwide&lt;/td&gt;
&lt;td&gt;a large but bounded dataset — small enough to index in memory per region, too large to scan per query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search queries&lt;/td&gt;
&lt;td&gt;very high rate, every map pan and "near me" tap&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;read-heavy&lt;/strong&gt;; the spatial lookup must be an indexed cell read, never a scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Writes (new / edited business)&lt;/td&gt;
&lt;td&gt;rare, and tolerant of a short delay&lt;/td&gt;
&lt;td&gt;the index and metadata can be &lt;strong&gt;updated lazily&lt;/strong&gt;; no write-path pressure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Density per area&lt;/td&gt;
&lt;td&gt;wildly uneven — a downtown cell may hold thousands, a rural cell a handful&lt;/td&gt;
&lt;td&gt;the index must cope with &lt;strong&gt;non-uniform density&lt;/strong&gt;, the trade-off that decides geohash vs quadtree&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, the system is &lt;strong&gt;read-heavy and slow-changing&lt;/strong&gt;, so after the index is right, scaling is the easy part: cache popular searches, serve metadata from read replicas, and never let a write path become the bottleneck. Second, there are &lt;strong&gt;two layers&lt;/strong&gt; that should be kept apart — the &lt;strong&gt;spatial layer&lt;/strong&gt; that answers &lt;em&gt;which businesses are near this point&lt;/em&gt; (the geo index, the hard problem) and the &lt;strong&gt;metadata layer&lt;/strong&gt; that answers &lt;em&gt;what are they&lt;/em&gt; (name, hours, rating, reviews — a normal, cacheable store). You have now pointed the interview at the real deep dives — the index, the query flow, and density — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: a client searches around a point, opens a business, and occasionally a business is written.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_nearby(lat, lng, radius, filters, page)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the core read — returns businesses within the radius, ranked, paginated.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_business(business_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;one-off read of a single business and its details.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;add_or_update_business(business)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;writes name, location, category and attributes; updates both the index and the metadata store.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search_by_viewport(bounding_box, filters)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;map-pan variant — returns businesses inside a rectangle instead of a radius.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model splits along the two layers. The &lt;strong&gt;geo index&lt;/strong&gt; is keyed by spatial cell; the &lt;strong&gt;business metadata&lt;/strong&gt; is a plain record read far more often than it is written:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;business&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;business_id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;lat&lt;/code&gt;, &lt;code&gt;lng&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;rating&lt;/code&gt;, &lt;code&gt;cell_key&lt;/code&gt; — the metadata record, stored in a normal, replicated store.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;geo_index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cell_key → [business_id, …]&lt;/code&gt; — the spatial index that turns a cell into the businesses inside it (geohash prefix, quadtree node, or S2 cell id).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;review&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(business_id, user, stars, text)&lt;/code&gt; — part of the metadata layer; scope the full review system for later in this round.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: the search service, geo index and metadata store
&lt;/h2&gt;

&lt;p&gt;Now the boxes. A &lt;strong&gt;client&lt;/strong&gt; sends a location and radius; a &lt;strong&gt;search service&lt;/strong&gt; turns that into a cell lookup, fetches candidates, refines and ranks them, and reads the details from a separate store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API gateway:&lt;/strong&gt; terminates the client request and routes &lt;code&gt;search_nearby&lt;/code&gt; to the search service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search service:&lt;/strong&gt; the heart of the system. It computes the &lt;strong&gt;covering cells&lt;/strong&gt; for the radius, reads the candidate business ids from the &lt;strong&gt;geo index&lt;/strong&gt;, computes &lt;strong&gt;exact distance&lt;/strong&gt; to drop false positives, ranks the survivors, and paginates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geospatial index:&lt;/strong&gt; maps a &lt;strong&gt;cell key to the business ids inside it&lt;/strong&gt; — a geohash prefix, a quadtree node, or an S2 cell id. This is the structure that makes "near me" an indexed read instead of a scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business metadata store:&lt;/strong&gt; the businesses themselves (name, hours, category, rating, reviews), fronted by &lt;strong&gt;read replicas&lt;/strong&gt; because it is read far more than written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache:&lt;/strong&gt; popular searches and hot &lt;code&gt;cell → businesses&lt;/code&gt; results sit in a cache, since the same busy areas are searched over and over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write path:&lt;/strong&gt; adding or editing a business is rare and slow-changing — it updates the metadata store and the geo index, and the caches expire naturally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that the design has &lt;strong&gt;two layers kept apart&lt;/strong&gt;: the spatial layer answers &lt;em&gt;which businesses are near&lt;/em&gt;, and the metadata layer answers &lt;em&gt;what they are&lt;/em&gt;. Naming that split — a search service over a cell index, backed by a separately scaled read store — is a signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fidn0vvayl6ha1bez9o6i.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fidn0vvayl6ha1bez9o6i.jpeg" alt="A proximity search architecture diagram showing clients sending a search-nearby request through an API gateway to a search service, which queries a geospatial index that maps map cells to business ids, fetches the matching businesses from a separate business-metadata store served by read replicas, and reads through a cache of popular searches and cell-to-businesses results, with a slow write path that updates both the geo index and the metadata store." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: the geospatial index (geohash vs quadtree vs S2)
&lt;/h2&gt;

&lt;p&gt;This is the decision the whole round is really about. The follow-up is the one from the top: &lt;em&gt;find every restaurant within one kilometre of me — how, without scanning all fifty million businesses?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start by killing the naive answers out loud. A &lt;strong&gt;full scan&lt;/strong&gt; computes a distance for every business on every search — impossible at this scale. A &lt;strong&gt;plain B-tree on latitude and longitude&lt;/strong&gt; looks tempting but barely helps: an index on &lt;code&gt;lat&lt;/code&gt; narrows you to a horizontal band of the whole planet, an index on &lt;code&gt;lng&lt;/code&gt; to a vertical one, and you are still left scanning a huge strip — two 1D indexes do not compose into a 2D range. The fix is to &lt;strong&gt;map 2D proximity to a single indexable key&lt;/strong&gt;, so that points near each other on the map are near each other in the index. There are three standard ways, and a strong answer names all three before picking one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Geohash&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Quadtree&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Google S2&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idea&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;interleave the bits of lat and lng and base-32 encode them into a &lt;strong&gt;prefix string&lt;/strong&gt;; nearby points share a prefix&lt;/td&gt;
&lt;td&gt;recursively split each square into &lt;strong&gt;four&lt;/strong&gt; quadrants until a cell holds at most N points&lt;/td&gt;
&lt;td&gt;number cells along a &lt;strong&gt;Hilbert curve&lt;/strong&gt; so each cell is a 64-bit id and nearby cells have nearby ids&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Density&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;fixed grid&lt;/strong&gt; — every cell is the same size regardless of how many points fall in it&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;adapts&lt;/strong&gt; — dense areas subdivide deeply, sparse areas stay coarse&lt;/td&gt;
&lt;td&gt;fine-grained levels; handles the whole globe and the poles cleanly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it lives&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;any string-prefix index — a plain DB column or Redis&lt;/td&gt;
&lt;td&gt;an in-memory tree that must be built and rebalanced&lt;/td&gt;
&lt;td&gt;a purpose-built library/service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best fit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;simple, portable, "good enough" nearby search&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;uneven density&lt;/strong&gt;, where a fixed grid breaks down&lt;/td&gt;
&lt;td&gt;planet-scale, Google/Tinder-class systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a Yelp-style service, &lt;strong&gt;start with a geohash&lt;/strong&gt;: it needs no special infrastructure — a geohash prefix is just a string column with a B-tree index, and a radius query becomes "the handful of geohash cells that cover the circle." Then say the one sentence that shows range: &lt;em&gt;all three turn 2D proximity into a 1D indexable key — geohash with a fixed grid of prefixes, a quadtree with an adaptive tree, S2 with a Hilbert-curve id — so I'd stay on geohash for simplicity and reach for a quadtree once density is uneven enough that a fixed cell size over-returns downtown and under-fills the countryside.&lt;/em&gt; That is the trade-off the round is testing, and it sets up the density deep dive.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Find every restaurant within one kilometre of me — how, without scanning all fifty million businesses?" I don't scan, and I don't rely on a plain lat/long index — two 1D indexes still leave a strip to scan. I give every business a spatial cell key: a geohash prefix, where nearby points share the prefix. A radius query becomes the small set of geohash cells that cover the circle, so I read only the businesses in those cells — a few hundred candidates, not fifty million — and then refine with exact distance. If density were very uneven I'd use a quadtree so cells adapt to how many businesses are in each area.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh06szq9nna1sdk5rtjqj.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh06szq9nna1sdk5rtjqj.jpeg" alt="A diagram comparing three ways to index points on a map so a nearby-search becomes a cell lookup: geohash encodes latitude and longitude into a base-32 prefix string so nearby points share a prefix on a fixed uniform grid, a quadtree recursively splits each square into four until a cell holds at most N points so dense areas subdivide deeply and sparse areas stay coarse, and Google S2 numbers cells along a Hilbert curve so each map cell becomes a single 64-bit id with nearby cells having nearby ids, all three turning two-dimensional proximity into a one-dimensional indexable key." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Search &amp;amp; discovery · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design Yelp proximity search out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: the search query flow
&lt;/h2&gt;

&lt;p&gt;The index decides &lt;em&gt;how points are stored&lt;/em&gt;; this decides &lt;em&gt;what actually happens on a single search&lt;/em&gt; and why the cells are only half the answer. The follow-up is about the edges: &lt;em&gt;a result sits just over a cell boundary from me — how do you not miss it, and how do you keep the results exact?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The flow is a &lt;strong&gt;coarse filter then a fine filter&lt;/strong&gt;: cells get you a small candidate set cheaply, and exact distance makes it correct.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compute the covering cells:&lt;/strong&gt; from the user's location and radius, work out the set of cells (geohash prefixes, quadtree nodes or S2 cells) that overlap the search circle. Pick a cell precision whose size is close to the radius so the set stays small.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include the neighbours:&lt;/strong&gt; a business just over a cell boundary is still within the radius, so always query the covering cells &lt;strong&gt;plus their adjacent cells&lt;/strong&gt;. This is the classic boundary bug — skip the neighbours and you silently drop nearby results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fetch the candidates:&lt;/strong&gt; read the business ids in those cells from the geo index. This is a bounded set — a few hundred or thousand — not the whole table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refine with exact distance:&lt;/strong&gt; the cells are rectangles, the search is a circle, so compute the &lt;strong&gt;exact haversine distance&lt;/strong&gt; from the user to each candidate and drop any that fall outside the true radius. The index is approximate on purpose; haversine makes the answer precise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank and paginate:&lt;/strong&gt; sort the survivors by a blend of &lt;strong&gt;distance, rating and relevance&lt;/strong&gt;, then return a page at a time so the client never pulls thousands of rows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the geo index is a &lt;strong&gt;coarse filter that trades precision for speed&lt;/strong&gt;, and the exact-distance step is what makes the result correct — and you must always query the neighbouring cells, because the one place a naive version breaks is a result sitting a few metres over a boundary. Instant "near me" is a cheap cell read followed by a precise distance check, never a scan.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A result sits just over a cell boundary from me — how do you not miss it, and how do you keep the results exact?" I never query just the single cell the user is in. I compute all the cells that cover the radius and include their immediate neighbours, so a business a few metres over a boundary is still a candidate. Then I compute the exact haversine distance for every candidate and drop the ones outside the true radius — the cells are a coarse filter, the distance check is the precise one. That way nothing near the edge is lost and nothing outside the circle sneaks in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Search &amp;amp; discovery · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend the covering-cells + haversine flow out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: density, scale and read-heaviness
&lt;/h2&gt;

&lt;p&gt;Getting the index and the query flow right keeps a single search correct; this decides whether the design holds up across a real map and a real load. The follow-up is the one that separates a memorised answer from an understood one: &lt;em&gt;one downtown cell has ten thousand places and a rural cell has two — how does your index handle both, and how do you serve a hundred times more reads than writes?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two forces shape this answer — &lt;strong&gt;uneven density&lt;/strong&gt; and a &lt;strong&gt;read-heavy, slow-changing&lt;/strong&gt; workload:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Density is the real trade-off:&lt;/strong&gt; a &lt;strong&gt;fixed-grid geohash&lt;/strong&gt; uses one cell size everywhere, so the dense downtown cell returns ten thousand candidates you must then distance-filter, while a rural cell of the same size holds almost nothing. A &lt;strong&gt;quadtree adapts&lt;/strong&gt;: it subdivides the dense region into many small cells and leaves the empty region as one large cell, so every cell holds a roughly bounded number of businesses. Saying that out loud — &lt;em&gt;fixed grid is simpler but struggles with uneven density; the quadtree subdivides only where it is dense&lt;/em&gt; — is the single most important sentence in this round.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tune geohash precision as a cheaper fix:&lt;/strong&gt; if you want to stay on geohash, use a &lt;strong&gt;longer prefix (smaller cell)&lt;/strong&gt; in dense areas and a shorter one in sparse areas, which approximates what the quadtree does for free.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The load is overwhelmingly reads:&lt;/strong&gt; people search far more than businesses change, so cache &lt;strong&gt;popular searches&lt;/strong&gt; and hot &lt;strong&gt;&lt;code&gt;cell → businesses&lt;/code&gt;&lt;/strong&gt; results; the same busy areas are queried constantly and the data behind them barely moves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale reads with replicas:&lt;/strong&gt; serve the business-metadata reads from &lt;strong&gt;read replicas&lt;/strong&gt;, and keep them separate from the spatial lookup so a hot metadata read does not contend with the index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writes are easy:&lt;/strong&gt; a new or edited business is rare and tolerant of a short delay, so the write path updates the index and metadata lazily and lets caches expire — there is no write bottleneck to engineer around.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the hard part of scaling this system is not throughput, it is &lt;strong&gt;density&lt;/strong&gt; — the index must not blow up in a dense cell or waste space in an empty one — and once the index handles density, the read-heavy load is absorbed almost entirely by &lt;strong&gt;caching and read replicas&lt;/strong&gt; over slow-changing data. Keep the geo index and the metadata store separate so each scales on its own terms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2c5mdbtrmtiofccser3.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo2c5mdbtrmtiofccser3.jpeg" alt="A diagram contrasting how a fixed-grid geohash and an adaptive quadtree handle uneven business density, showing a dense downtown cell holding ten thousand businesses and a rural cell holding two, where the fixed grid either over-returns from the dense cell or under-fills the sparse one while the quadtree subdivides the dense region into smaller cells and leaves the sparse region coarse, alongside the read-scaling picture of a cache for popular searches and read replicas serving a workload with far more reads than writes." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the proximity-search round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just index latitude and longitude and query a range?"&lt;/strong&gt; → Because two separate 1D indexes don't compose into a 2D range — each narrows one axis to a planet-long strip you'd still scan. You need a single spatial key (geohash, quadtree or S2) so points near on the map are near in the index. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Geohash, quadtree or S2 — which and why?"&lt;/strong&gt; → Geohash for a simple, portable index that's just a string column; a quadtree when density is uneven, because it subdivides dense areas and leaves sparse ones coarse; S2 for planet-scale with a Hilbert-curve cell id. All three map 2D proximity to a 1D indexable key. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A result sits just over a cell boundary — how do you not miss it?"&lt;/strong&gt; → Query the covering cells plus their neighbours, then compute exact haversine distance and drop anything outside the true radius; the cells are a coarse filter and the distance check is the precise one. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"One cell has ten thousand businesses and another has two — how does your index cope?"&lt;/strong&gt; → A fixed-grid geohash over-returns in the dense cell and wastes the sparse one; a quadtree adapts by subdividing only where it is dense, or you tune geohash precision per area to approximate it. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"This is a hundred times more reads than writes — how do you scale?"&lt;/strong&gt; → Cache popular searches and hot cell-to-businesses results, serve metadata from read replicas, and keep the geo index separate from the metadata so each scales independently; writes are rare and can be lazy. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do the proximity service end to end on paper — requirements, estimate, API, data model, and the search-service / geo-index / metadata boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill the geospatial index: why a scan and a plain lat/long index both fail, how a geohash prefix encodes a cell, how a quadtree subdivides, and what S2 adds — and say in one sentence when you'd pick each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own the query flow and density — covering cells plus neighbours, the exact haversine refine, ranking and pagination, and the fixed-grid-vs-quadtree density trade-off plus caching and read replicas. Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the proximity service is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design Yelp step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (search businesses within a radius of a location and get them ranked with name, category, rating and distance; open a business for details; add and edit businesses; the system is read-heavy and business data changes slowly); estimate to show that the load is overwhelmingly reads against tens of millions of mostly static businesses with wildly uneven density; define the small API (search_nearby, get_business, add_or_update_business, a viewport variant) over two layers — a spatial index and a business-metadata store; draw the design (clients → API → search service that computes covering cells, reads candidates from a geospatial index, refines by exact distance, ranks and paginates, backed by read replicas and a cache); then deep-dive the geospatial index (geohash vs quadtree vs S2), the query flow (covering cells plus neighbours then haversine), and density plus read-scaling. Finish on the follow-ups about boundaries, density and read-heaviness.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you find nearby places without scanning every business?
&lt;/h3&gt;

&lt;p&gt;By giving every business a spatial cell key so "near me" becomes an indexed cell lookup instead of a scan. A full scan computes a distance for every row on every search, which is impossible at tens of millions of businesses, and a plain B-tree on latitude and longitude barely helps because two separate 1D indexes do not compose into a 2D range — each leaves a planet-long strip to scan. Instead you encode each location into a geohash prefix (or a quadtree node, or an S2 cell id) so nearby points share a key, turn a radius into the small set of cells that cover it, read only the businesses in those cells, and then refine with exact distance. You go from fifty million rows to a few hundred candidates.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a geohash?
&lt;/h3&gt;

&lt;p&gt;A geohash is a way of encoding a latitude/longitude pair into a short string so that nearby points share a common prefix. You interleave the bits of the latitude and longitude and base-32 encode the result; the longer the string, the smaller and more precise the cell it names. Because nearness on the map becomes a shared string prefix, a geohash works with any ordinary string index — a plain database column or a key in Redis — and a "find nearby" query becomes "the handful of geohash cells that cover this circle." Its limitation is that it is a fixed grid: every cell at a given precision is the same size regardless of how many businesses fall inside it, which is why uneven density pushes designs toward a quadtree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Geohash vs quadtree vs S2 — what's the trade-off?
&lt;/h3&gt;

&lt;p&gt;All three turn two-dimensional proximity into a one-dimensional indexable key; they differ in how they handle density and where they live. A geohash is a fixed grid of equal cells encoded as prefix strings — simplest and portable, it runs on a plain index, but a uniform cell size over-returns in dense areas and wastes space in sparse ones. A quadtree recursively splits each square into four until a cell holds at most N points, so it adapts to density — dense regions subdivide deeply, sparse regions stay coarse — at the cost of maintaining an in-memory tree. Google S2 numbers cells along a Hilbert curve so each map cell is a 64-bit id with nearby cells sharing nearby ids, handling the whole globe cleanly for planet-scale systems. Pick geohash for simplicity, a quadtree for uneven density, S2 at global scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you handle uneven density and a read-heavy load?
&lt;/h3&gt;

&lt;p&gt;Density is the real trade-off. A fixed-grid geohash uses one cell size everywhere, so a downtown cell can hold ten thousand businesses while a rural cell holds two — the dense cell returns far too many candidates and the sparse one too few. A quadtree solves this by subdividing only where it is dense and leaving empty regions coarse, so each cell holds a bounded number of businesses; alternatively you tune geohash precision per area to approximate the same effect. The load itself is overwhelmingly reads against slowly changing data, so you cache popular searches and hot cell-to-businesses results, serve business metadata from read replicas, and keep the geo index separate from the metadata store so each scales on its own and a rare write never becomes a bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what a geohash is. The round is won on turning a radius into cells out loud.&lt;/h3&gt;

&lt;p&gt;Pick the Local Business Search (proximity) round, draw the search-service, geo-index and metadata design on the canvas, and defend it out loud to a voice interviewer that keeps asking "find every restaurant within a kilometre without scanning fifty million rows — how?" and "one cell has ten thousand places and another has two — now what?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the Proximity Search round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>dataengineering</category>
      <category>walkin</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Design Dropbox: the file storage and sync system design walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 04:07:55 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-dropbox-the-file-storage-and-sync-system-design-walkthrough-41o1</link>
      <guid>https://dev.to/gowthampotureddi/design-dropbox-the-file-storage-and-sync-system-design-walkthrough-41o1</guid>
      <description>&lt;h2&gt;
  
  
  Design Dropbox: the file storage and sync system design walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design Dropbox&lt;/strong&gt; sounds like one line — put my files in the cloud and keep them the same on every device — and that one line hides the whole interview. Picture it as upload and download and you reach for a form, a blob store and a save button; picture it as &lt;strong&gt;file storage and sync&lt;/strong&gt; where a 1 GB video has one byte changed, a laptop and a phone must both end up identical, and two machines edited the same file while both were offline, and it becomes one of the more deceptively deep problems in distributed systems. This is one of the most revealing &lt;strong&gt;file sync system design&lt;/strong&gt; prompts because it is not about storing bytes first — it is about &lt;strong&gt;sync efficiency and consistency&lt;/strong&gt;: moving only what changed, converging every device on the same file, and never silently losing an edit.&lt;/p&gt;

&lt;p&gt;The round turns on one structural insight. "Store my files and sync them" hides the fact that a file is the wrong unit of work — if you treat it as one opaque blob, a one-byte edit forces a gigabyte back over the wire and every save fights every other save. The reframe that unlocks the whole design is to &lt;strong&gt;stop treating a file as one blob and treat it as an ordered list of content-addressed chunks&lt;/strong&gt;: split each file into fixed-size pieces, identify each piece by the hash of its contents, and now sync is just "which chunk hashes changed." Get that reframe — a file is a list of chunk hashes, so you move chunks not files, store each unique chunk once, and let a small metadata service be the source of truth while the bytes live in a blob store — and the rest of the &lt;strong&gt;system design interview&lt;/strong&gt; is deciding how a device learns something changed, how it pulls only what it is missing, and what happens when two devices disagree. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2File2xt6nfs0okfgl9umh.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2File2xt6nfs0okfgl9umh.jpeg" alt="Title card for a system design interview walkthrough on designing Dropbox, showing a large file split into a row of fixed-size chunks where only one chunk is highlighted as changed and uploaded while the rest are skipped, syncing to two other devices, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the file-sync round is actually run
&lt;/h2&gt;

&lt;p&gt;A file storage and sync 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 "upload the file, download the file," the question becomes &lt;em&gt;what happens when a huge file barely changes&lt;/em&gt; and &lt;em&gt;how do two devices stay identical without clobbering each other&lt;/em&gt;, 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 chunking, the sync flow and conflicts, not on the sharing-permissions screen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "just upload it to storage" stand. "You change one byte in the middle of a 1 GB file, and your phone is asleep in your pocket — what actually gets uploaded, how does your phone find out, and what does it download when it wakes up?" That question — efficient, consistent sync of a large file across devices — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — file sizes, edit rates and device counts versus total bytes stored, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: the clients, the metadata service, the notification service, and the blob store the bytes actually live in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe a file as an ordered list of content-addressed chunks and reach chunking, dedup and delta sync), &lt;strong&gt;communication&lt;/strong&gt; (did you say why a one-byte edit must not re-upload a gigabyte, and why bytes go straight to blob storage while app servers touch only metadata, each in a sentence), &lt;strong&gt;technical execution&lt;/strong&gt; (fixed-size hashed chunks, a metadata service holding the chunk list and version sharded by user, a notification service, presigned direct-to-blob transfer, conflicted copies), and &lt;strong&gt;time management&lt;/strong&gt; (did you spend your 45 minutes on sync efficiency and conflicts, not on previews and thumbnails). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what a file storage and sync service must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Users upload and download files; a file is kept identical across all of a user's devices; edits sync automatically and quickly; files can be shared with other users; version history is kept; the client works offline and reconciles on reconnect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very large files, selective sync (only some folders on a device), previews/thumbnails, trash and restore — call these out, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Sync efficiency&lt;/strong&gt; — never re-send data that did not change; &lt;strong&gt;durability&lt;/strong&gt; — a stored file is never lost; &lt;strong&gt;consistency&lt;/strong&gt; — every device converges on the same bytes and no edit is silently dropped; high availability; scale to enormous total bytes and many devices per user.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"This is a sync-efficiency and consistency problem, not upload/download CRUD — if I treat a file as one blob, a one-byte edit costs a gigabyte, so I'll treat each file as an ordered list of content-addressed chunks and sync only the chunks that changed."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving one thing: &lt;strong&gt;the bytes are enormous but mostly unchanged between versions, and the metadata is tiny&lt;/strong&gt; — so the design must move deltas, not files, and keep the two apart.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Files and devices per user&lt;/td&gt;
&lt;td&gt;many files, a handful of devices&lt;/td&gt;
&lt;td&gt;a bounded fan-out per account — the unit of work is &lt;strong&gt;one user's files across their devices&lt;/strong&gt;, not the whole fleet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File size and edit rate&lt;/td&gt;
&lt;td&gt;files range from kilobytes to many gigabytes; edits usually touch a small fraction of a large file&lt;/td&gt;
&lt;td&gt;most bytes are &lt;strong&gt;unchanged on edit&lt;/strong&gt; — a whole-file re-upload would waste almost all of it, so &lt;strong&gt;delta sync&lt;/strong&gt; is the win&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total bytes stored&lt;/td&gt;
&lt;td&gt;petabyte-scale and growing&lt;/td&gt;
&lt;td&gt;bytes belong in a &lt;strong&gt;blob/object store&lt;/strong&gt; that scales independently, not in your application database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata rows&lt;/td&gt;
&lt;td&gt;one small record per file plus its chunk list&lt;/td&gt;
&lt;td&gt;metadata is &lt;strong&gt;small and consistent&lt;/strong&gt; — &lt;strong&gt;shard by user id&lt;/strong&gt; and keep it off the data path&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, &lt;strong&gt;there are two planes&lt;/strong&gt;: a small, consistent &lt;strong&gt;metadata plane&lt;/strong&gt; (what a file is — its chunk list and version) and a huge, content-addressed &lt;strong&gt;data plane&lt;/strong&gt; (the chunk bytes themselves). Keep them separate — app servers manage metadata, bytes go straight to the blob store. Second, because most of a large file is unchanged between versions, &lt;strong&gt;the only thing worth syncing is the delta&lt;/strong&gt;, which is exactly what content-addressed chunking gives you for free. You have now pointed the interview at the real deep dives — chunking and delta sync, the notification/sync flow, and conflicts — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: a client asks what the file is now, uploads only the chunks that are new, and subscribes for changes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_file_metadata(file_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns the file's current version and its &lt;strong&gt;ordered list of chunk hashes&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;has_chunks(hash[])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the dedup check — tells the client which of these chunk hashes the store already has, so it uploads only the rest.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;put_chunk(hash, bytes)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;uploads one chunk's bytes &lt;strong&gt;directly to the blob store&lt;/strong&gt; via a presigned URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;commit_file(file_id, chunk_hash[], base_version)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;records a new file version as a new ordered chunk-hash list, against the version the client last saw.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_chunk(hash)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;downloads one chunk (served via CDN) to reassemble the file.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subscribe_changes(user_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;opens a long-poll/websocket so the device is told when anything under the account changes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model is thin, and the split between metadata and bytes is the whole point:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;file_id&lt;/code&gt;, &lt;code&gt;owner&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, &lt;strong&gt;ordered &lt;code&gt;chunk_hash[]&lt;/code&gt;&lt;/strong&gt; — the metadata source of truth for &lt;em&gt;what the file is&lt;/em&gt;; sharded by user id.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chunk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;hash&lt;/code&gt; → the chunk's &lt;strong&gt;bytes in the blob store&lt;/strong&gt;; &lt;strong&gt;content-addressed&lt;/strong&gt; so an identical chunk is stored once and referenced many times (deduplication).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(user_id, device_id, last_synced_version)&lt;/code&gt; — what each device has already caught up to, so catch-up is a cheap diff.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;version&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the prior &lt;code&gt;chunk_hash[]&lt;/code&gt; lists per file — history, so nothing is ever truly overwritten.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: metadata plane, data plane and notifications
&lt;/h2&gt;

&lt;p&gt;Now the boxes. The design is defined by a line drawn through the middle of it — &lt;strong&gt;metadata on one plane, bytes on another&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata service:&lt;/strong&gt; the source of truth for &lt;em&gt;what each file is&lt;/em&gt; — its version and ordered chunk-hash list — &lt;strong&gt;sharded by user id&lt;/strong&gt;. It is small, consistent, and never handles file bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notification service:&lt;/strong&gt; holds long-lived connections (long-poll or websocket) and tells a device &lt;em&gt;"something under your account changed"&lt;/em&gt; so it knows to sync. It pushes a signal, not the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Blob / object store:&lt;/strong&gt; where the &lt;strong&gt;chunk bytes&lt;/strong&gt; actually live, content-addressed by hash, scaling independently as petabytes. Clients read and write chunks &lt;strong&gt;directly&lt;/strong&gt; here via &lt;strong&gt;presigned URLs&lt;/strong&gt; — the app servers never sit in the byte path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDN:&lt;/strong&gt; downloads of chunks are served through a CDN so bytes come from near the user, not from one region.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunk index / dedup:&lt;/strong&gt; a lookup of hashes the store already has, so &lt;code&gt;has_chunks&lt;/code&gt; can tell a client to skip anything already stored.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that &lt;strong&gt;app servers never touch file bytes&lt;/strong&gt;: the client chunks and hashes locally, the metadata service tells it which chunks are new, and the bytes flow straight to the blob store via a presigned URL. Naming that split — a light control plane for metadata and notifications, a heavy data plane that scales with the object store and CDN — is a signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ttq5wdagsek6dj2zx7f.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4ttq5wdagsek6dj2zx7f.jpeg" alt="A file storage and sync architecture diagram showing clients talking to a metadata service that stores each file's version and ordered chunk-hash list sharded by user id and to a notification service over long-poll or websocket, while the file bytes flow directly between the client and a blob or object store using presigned URLs, with downloads served through a CDN, and a chunk index used to deduplicate identical chunks." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: chunking, deduplication and delta sync
&lt;/h2&gt;

&lt;p&gt;This is the decision the whole round is really about. The follow-up is the one-byte edit: &lt;em&gt;you change one byte in the middle of a 1 GB file — what actually gets uploaded?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start by killing the naive answer out loud: &lt;strong&gt;re-uploading the whole file is unacceptable&lt;/strong&gt;. A one-byte change does not mean a gigabyte of new data, so sending a gigabyte wastes bandwidth, battery and time. The fix is to make the file &lt;strong&gt;addressable in pieces&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chunk the file:&lt;/strong&gt; split it into &lt;strong&gt;fixed-size chunks&lt;/strong&gt; (~4 MB is a reasonable default). Chunking is what makes a partial change a small change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash each chunk:&lt;/strong&gt; compute a hash of each chunk's contents (e.g. SHA-256). The hash &lt;strong&gt;is&lt;/strong&gt; the chunk's identity — this is &lt;strong&gt;content addressing&lt;/strong&gt;. Two chunks with the same bytes have the same hash, anywhere, for anyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The file is a list of hashes:&lt;/strong&gt; metadata stores the file as an &lt;strong&gt;ordered list of chunk hashes&lt;/strong&gt; plus a version. That list, not the bytes, is what the control plane reasons about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate:&lt;/strong&gt; because chunks are addressed by content, an identical chunk is &lt;strong&gt;stored once&lt;/strong&gt; in the blob store and referenced by every file and user that contains it — saving both storage and upload bandwidth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delta sync:&lt;/strong&gt; editing a file re-chunks it; only the chunks whose &lt;strong&gt;hash changed&lt;/strong&gt; are uploaded or downloaded. Change one byte in a 1 GB file and you re-chunk, find that &lt;strong&gt;one&lt;/strong&gt; ~4 MB chunk has a new hash, upload that single chunk, and commit a new hash list. One chunk moves, not a gigabyte.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: &lt;strong&gt;content addressing turns sync into set arithmetic&lt;/strong&gt;. "What do I need to upload?" is just "which of my chunk hashes does the store not already have?", and "what do I need to download?" is "which hashes in the new version am I missing?" The whole efficiency of the system falls out of identifying chunks by their contents.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You change one byte in the middle of a 1 GB file — what actually gets uploaded?" The client re-chunks the file into fixed-size pieces and hashes each one. Only the single ~4 MB chunk containing that byte gets a new hash; every other chunk's hash is unchanged and already in the store. So the client uploads that one chunk and commits a new version that is just a new ordered list of chunk hashes — one chunk on the wire, not a gigabyte. Identical chunks are stored once and reused, so even that upload is skipped if the store already has it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz7sw17r2yjjy9h3wyamr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz7sw17r2yjjy9h3wyamr.jpeg" alt="A diagram showing a file split into fixed-size four-megabyte chunks each labelled with a content hash, where editing the file re-chunks it and only the single chunk whose hash changed is uploaded while identical chunks are deduplicated and stored once, and the file itself is represented in metadata as an ordered list of chunk hashes." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Storage &amp;amp; files · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design Dropbox out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: metadata, change notification and the sync flow
&lt;/h2&gt;

&lt;p&gt;Chunking decides &lt;em&gt;what&lt;/em&gt; moves; this decides &lt;em&gt;how a device even learns it should move anything&lt;/em&gt;, and how the two planes work together. The follow-up is about discovery: &lt;em&gt;how does a second device even know a file changed?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is a &lt;strong&gt;notification service plus a diff against the metadata&lt;/strong&gt;, with bytes pulled straight from the blob store:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata is the source of truth for state:&lt;/strong&gt; the metadata service holds each file's current version and chunk-hash list, per user. It knows &lt;em&gt;what the file is&lt;/em&gt;; the blob store knows &lt;em&gt;the bytes&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A change notification wakes the device:&lt;/strong&gt; when a file changes, the metadata is updated and the &lt;strong&gt;notification service&lt;/strong&gt; pushes a lightweight signal — over long-poll or websocket — to the user's other devices: &lt;em&gt;"something under your account changed."&lt;/em&gt; It sends the nudge, not the data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The device pulls the delta:&lt;/strong&gt; on that signal the device calls &lt;code&gt;get_file_metadata&lt;/code&gt;, gets the &lt;strong&gt;new ordered chunk-hash list&lt;/strong&gt;, and &lt;strong&gt;diffs it against its local list&lt;/strong&gt;. The chunks whose hashes it does not already have are exactly what it must fetch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download only the missing chunks:&lt;/strong&gt; it fetches just those chunks from the blob store (via CDN), then &lt;strong&gt;reassembles&lt;/strong&gt; the file in the order the hash list specifies. Unchanged chunks are already on disk and are never re-downloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload mirrors it:&lt;/strong&gt; to push a local edit, the client re-chunks, calls &lt;code&gt;has_chunks&lt;/code&gt; to learn which hashes are new, uploads only those &lt;strong&gt;directly to the blob store&lt;/strong&gt;, then &lt;code&gt;commit_file&lt;/code&gt; records the new version. Offline edits are buffered and run the same diff on reconnect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the &lt;strong&gt;notification carries no content&lt;/strong&gt; — it only triggers a pull, and the pull is a cheap diff of two hash lists. This keeps the control plane tiny and lets the data plane (blob store + CDN) carry the weight. A device that was asleep for a week catches up with exactly the same diff as one that missed a single edit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How does a second device even know a file changed?" It holds a long-poll or websocket connection to a notification service. When a file under the account changes, that service pushes a small 'something changed' signal — never the bytes. The device then pulls the updated metadata, diffs the new ordered chunk-hash list against what it has locally, and downloads only the chunks it is missing from the blob store through the CDN, reassembling the file from the hash list. A week offline or one edit behind, it is the same cheap diff.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Storage &amp;amp; files · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend the metadata/data-plane split out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: scale, consistency and conflicts
&lt;/h2&gt;

&lt;p&gt;Chunking and the sync flow keep a well-behaved account consistent; this decides what happens at petabyte scale and when two devices genuinely disagree. The follow-up is the collision: &lt;em&gt;two of your devices edit the same file while both are offline — what happens when they reconnect?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The scale answers come from the two-plane split; the consistency answer is to &lt;strong&gt;detect divergence and keep both copies&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bytes never touch the app servers:&lt;/strong&gt; uploads and downloads go &lt;strong&gt;directly to the blob store via presigned URLs&lt;/strong&gt;, so the control plane stays small and the data plane scales with the object store and CDN. App servers move metadata, not gigabytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shard metadata by user id:&lt;/strong&gt; a user's files and devices are the natural unit of coordination; different users are independent, so metadata scales horizontally and a hot account is one shard's load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detect conflicts by version lineage:&lt;/strong&gt; every commit is made &lt;strong&gt;against a base version&lt;/strong&gt;. If two devices both edited from version N while offline, on reconnect the server sees &lt;strong&gt;two different children of the same version&lt;/strong&gt; — that is a conflict, not a valid linear update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never silently overwrite — make a conflicted copy:&lt;/strong&gt; rather than letting one device clobber the other, the server keeps both: the file and a &lt;strong&gt;&lt;code&gt;(Device B's conflicted copy)&lt;/code&gt;&lt;/strong&gt;. Both users' work survives, and the person decides how to merge. Last-write-wins would silently destroy an edit, which is unacceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning and resumable uploads:&lt;/strong&gt; because every version is just a stored chunk-hash list, &lt;strong&gt;history is free&lt;/strong&gt; and restores are cheap; and because uploads are chunked, a dropped connection &lt;strong&gt;resumes at the next un-acked chunk&lt;/strong&gt; instead of restarting the file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: &lt;strong&gt;consistency here means convergence without loss&lt;/strong&gt;. A single writer's change propagates by notify-then-pull; concurrent writers are detected by divergent version lineage and resolved by preserving both, never by picking a winner that deletes work. The chunk model makes all of it — dedup, delta, history, resumable uploads — the same mechanism seen from different angles.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Two of your devices edit the same file while both are offline — what happens when they reconnect?" Each committed its edit against the same base version, so the server sees two different children of that version — a genuine conflict. It never silently overwrites: it keeps the original and writes the other as a conflicted copy named for the device, so neither person's work is lost, and versioning retains the history. A normal single-writer change, by contrast, is just notify-then-pull-the-delta.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fquye5ba2l8jm61co3s74.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fquye5ba2l8jm61co3s74.jpeg" alt="A diagram showing two devices editing the same file from the same base version while both are offline, each producing a divergent new version, so on reconnect the server detects the divergence and keeps both by creating a conflicted copy rather than silently overwriting, while a normal single-writer change simply notifies the other device to pull the delta." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the file-sync round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just re-upload the file when it changes?"&lt;/strong&gt; → Because a one-byte edit does not mean a gigabyte of new data. Chunk the file into fixed-size pieces, hash each (content addressing), and upload only the chunk whose hash changed — one ~4 MB chunk, not the whole file. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How do identical files or chunks avoid being stored twice?"&lt;/strong&gt; → Chunks are addressed by the hash of their contents, so an identical chunk has the same hash everywhere and is stored once and referenced many times — deduplication across versions, files and users. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How does another device know a file changed, and what does it fetch?"&lt;/strong&gt; → A notification service pushes a lightweight 'something changed' signal over long-poll/websocket; the device pulls the new chunk-hash list, diffs it against its local list, and downloads only the missing chunks from the blob store via CDN. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why don't the app servers handle the file bytes?"&lt;/strong&gt; → To keep the control plane light and the data plane scalable: metadata (small, consistent, sharded by user) goes through app servers, but bytes go directly to the blob store via presigned URLs and are served through a CDN. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Two devices edit offline — who wins?"&lt;/strong&gt; → Neither is silently overwritten. Both committed against the same base version, so the server detects divergent lineage and keeps both as a conflicted copy, with versioning preserving history. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do the file-sync service end to end on paper — requirements, estimate, API, data model, and the metadata-service/notification-service/blob-store boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill chunking: fixed-size chunks, content-addressed hashes, a file as an ordered hash list, deduplication, and delta sync. Say the one-byte-in-a-gigabyte answer in a single sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own the sync flow and conflicts — notify-then-pull, diff the hash lists, download only missing chunks, and the conflicted-copy resolution for offline edits. Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the file-sync service is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design Dropbox step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (upload/download files, keep them identical across a user's devices, sync edits automatically, share with others, version history, work offline then reconcile — with the hard rules of sync efficiency, durability, and convergence with no silent loss); estimate to show that bytes are huge but mostly unchanged on edit while metadata is tiny, which forces delta sync and a two-plane split; define the small API (get_file_metadata, has_chunks, put_chunk, commit_file against a base version, get_chunk, subscribe_changes) over a file/chunk/device/version model; draw the design (clients to a metadata service sharded by user id and a notification service, with chunk bytes flowing directly to a blob store via presigned URLs and served through a CDN); then deep-dive chunking + dedup + delta sync, the notification/sync flow, and scale + conflicts. Finish on the follow-ups about one-byte edits and offline collisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does file sync avoid re-uploading the whole file when it barely changes?
&lt;/h3&gt;

&lt;p&gt;By splitting each file into fixed-size chunks (around 4 MB) and identifying each chunk by a hash of its contents. The file is stored in metadata as an ordered list of those chunk hashes. When you edit the file, the client re-chunks it and compares hashes: only the chunks whose hash changed are new, so only those are uploaded. A one-byte change in a 1 GB file produces exactly one new chunk hash, so roughly one 4 MB chunk moves instead of a gigabyte. This is called delta sync, and it falls directly out of addressing chunks by their content.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is content-addressable chunking and deduplication?
&lt;/h3&gt;

&lt;p&gt;Content-addressable means a chunk's identity is the hash of its bytes, not a name or location. Two chunks with identical contents produce an identical hash anywhere in the system, for any user. Deduplication uses that property: an identical chunk is stored once in the blob store and simply referenced by every file, version or user that contains it, instead of being stored again. So the same attachment synced by a hundred people, or the unchanged 99% of an edited file, costs storage and bandwidth only once. The file itself is just an ordered list of these content hashes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does another device know a file changed, and what does it download?
&lt;/h3&gt;

&lt;p&gt;Each device holds a long-poll or websocket connection to a notification service. When a file under the account changes, the metadata is updated and the notification service pushes a small "something changed" signal — never the file bytes. The device then pulls the file's new ordered chunk-hash list from the metadata service, diffs it against the list it already has locally, and downloads only the chunks it is missing from the blob store, served through a CDN, before reassembling the file in order. Whether the device was one edit behind or a week offline, it is the same cheap diff of two hash lists.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are conflicts and offline edits handled?
&lt;/h3&gt;

&lt;p&gt;Every change is committed against the version the client last saw. For a single writer, that is a clean linear update that simply notifies the other devices to pull the delta. When two devices edit the same file while both are offline, each commits against the same base version, so on reconnect the server sees two divergent children of that version — a genuine conflict. Rather than silently overwrite one with the other (which would destroy real work), it keeps both: the original and a conflicted copy named for the second device, leaving the person to merge them. Version history is retained throughout, and because uploads are chunked, a dropped connection resumes at the next chunk rather than restarting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what chunking and delta sync mean. The round is won on reasoning through sync efficiency out loud.&lt;/h3&gt;

&lt;p&gt;Pick the File Storage and Sync round, draw the metadata-plane, data-plane and notification design on the canvas, and defend it out loud to a voice interviewer that keeps asking "you change one byte in a 1 GB file — what moves?" and "two devices edit offline — who wins?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the File Storage and Sync round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>walkin</category>
      <category>dataengineering</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Design a real-time analytics system: the stream-processing walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:59:42 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-a-real-time-analytics-system-the-stream-processing-walkthrough-2mpe</link>
      <guid>https://dev.to/gowthampotureddi/design-a-real-time-analytics-system-the-stream-processing-walkthrough-2mpe</guid>
      <description>&lt;h2&gt;
  
  
  Design a real-time analytics system: the stream-processing walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design a real-time analytics system&lt;/strong&gt; sounds like a reporting feature — show some live numbers on a dashboard — and that framing is the trap. Picture it as a table you query on demand and you reach for a database and a &lt;code&gt;GROUP BY&lt;/code&gt;; picture it as a &lt;strong&gt;firehose of events&lt;/strong&gt; arriving a billion a minute, each one needing to move a counter that a dashboard reads a few seconds later, and it becomes one of the defining streaming problems in distributed systems. This is one of the most revealing &lt;strong&gt;real-time analytics system design&lt;/strong&gt; prompts because the hard part is not storing the data — it is that you can never afford to scan the raw events when the dashboard loads. The numbers have to be computed &lt;em&gt;as the data arrives&lt;/em&gt; and served pre-aggregated.&lt;/p&gt;

&lt;p&gt;The round turns on one structural insight. "Show live metrics" hides the fact that the input is &lt;strong&gt;unbounded and bursty&lt;/strong&gt; — it never ends, and it spikes without warning — so the only workable shape is a pipeline that aggregates continuously: &lt;strong&gt;ingest → process → store → serve&lt;/strong&gt;. Raw events pour into a durable log; a stream processor rolls them up into windowed aggregates the instant they land; those rollups go to a store tuned to answer a dashboard query in milliseconds. Say that spine out loud and the &lt;strong&gt;system design interview&lt;/strong&gt; becomes three decisions inside it: how to absorb the firehose without dropping data, how to aggregate a never-ending stream &lt;em&gt;correctly&lt;/em&gt; when events arrive late and out of order, and how to serve queries sub-second over an enormous history while the numbers stay trustworthy. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faf3lbmsarflbylv5dntz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faf3lbmsarflbylv5dntz.jpeg" alt="Title card for a system design interview walkthrough on designing a real-time analytics system, showing a firehose of events flowing into a partitioned log, then a stream processor computing windowed counts, then a dashboard tile that updates every few seconds, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the real-time analytics round is actually run
&lt;/h2&gt;

&lt;p&gt;A real-time analytics round runs about 60 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. The extra time over a 45-minute round is not slack; it is because this problem has three substantial deep dives that each deserve real minutes, and a weak candidate spends the hour drawing one giant pipeline while a strong one budgets it across ingestion, aggregation and serving. The one-line problem opens onto deep water fast: the moment you draw "events go into a thing that counts them," the question becomes &lt;em&gt;what counts as the right count&lt;/em&gt;, and every answer after that compounds from how you handle time, lateness and scale.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "we aggregate the events" stand. "Count the unique users in the last five minutes across a billion events a minute — exactly, or is approximate OK? Walk me through where that number actually gets computed and stored." That question — correctness and cost under a real firehose — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a 60-minute clock, and a follow-up after every decision you make.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — events per second and the burst factor versus query volume, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: producers, the ingestion log, the stream processor, the serving store, and the query layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe it as &lt;em&gt;pre-aggregate a stream, don't scan raw events&lt;/em&gt;, and reach for windows and approximation), &lt;strong&gt;communication&lt;/strong&gt; (did you say in one sentence why approximate counts are fine for a live dashboard but a batch layer earns the exact numbers), &lt;strong&gt;technical execution&lt;/strong&gt; (a partitioned log, a stream processor doing event-time windows with watermarks, rollups in an OLAP store, at-least-once plus idempotent writes), and &lt;strong&gt;time management&lt;/strong&gt; (did you spend your 60 minutes across ingestion, aggregation and serving, not sink the hour into watermark edge cases). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what a real-time analytics system must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ingest a high-volume stream of events; compute aggregate metrics per key over time windows (counts, sums, distinct counts, top-K); serve live dashboards and ad-hoc queries over those metrics with low latency; support both a recent real-time view and a historical view.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Alerting / anomaly detection on the metrics, raw-event drill-down, custom user-defined metrics, exactly-once exact reporting for billing — call these out, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Freshness&lt;/strong&gt; — metrics reflect reality within seconds; the pipeline is &lt;strong&gt;write/aggregate-heavy and read-light&lt;/strong&gt;; it scales horizontally to a firehose that bursts without warning; nothing is silently dropped under load; durable and highly available; dashboard queries return in well under a second.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"This is a streaming aggregation problem, not reporting-over-a-table — I can't scan raw events per dashboard load at this volume, so I pre-aggregate the stream as it arrives and serve the rollups, and I'll decide per metric whether approximate-and-fast or exact-and-slightly-delayed is the requirement."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving one thing: &lt;strong&gt;the load is a continuous write/aggregate stream, not a read workload&lt;/strong&gt; — and that one asymmetry drives every later choice.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Event rate&lt;/td&gt;
&lt;td&gt;very high and bursty — say ~1M–1B events/sec at peak, spiking on events like a flash sale&lt;/td&gt;
&lt;td&gt;the system is &lt;strong&gt;write/aggregate-bound&lt;/strong&gt;; it must absorb bursts without dropping data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query rate&lt;/td&gt;
&lt;td&gt;low — a handful of dashboards and analysts, maybe tens to hundreds of queries/sec&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;read-light&lt;/strong&gt; — so you can afford to pre-compute heavily and serve cheap reads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw retention&lt;/td&gt;
&lt;td&gt;storing every raw event forever is enormous (petabytes)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;roll up and expire raw&lt;/strong&gt; — keep fine-grained rollups hot, coarsen or drop old raw&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit of work&lt;/td&gt;
&lt;td&gt;one metric for one key over one time window&lt;/td&gt;
&lt;td&gt;the &lt;strong&gt;(key, window)&lt;/strong&gt; tuple is what you partition on and pre-aggregate — the whole design orbits it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, because writes dwarf reads, you &lt;strong&gt;pre-compute aggressively&lt;/strong&gt;: do the expensive aggregation once as data streams in, so every dashboard read is a cheap lookup of a rollup rather than a scan. Second, there are &lt;strong&gt;two regimes&lt;/strong&gt; you must name: &lt;strong&gt;freshness&lt;/strong&gt; (a live dashboard wants seconds-old numbers and can tolerate a small, bounded approximation) versus &lt;strong&gt;correctness&lt;/strong&gt; (billing or official reporting wants exact numbers and can tolerate minutes of delay). Deciding which a given metric needs is what later justifies HyperLogLog over exact counting and lambda over a single pipeline. You have now pointed the interview at the real deep dives — ingestion, aggregation, and serving — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: producers push events in, consumers read pre-aggregated metrics out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ingest_event(event)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;accepts one event &lt;code&gt;(timestamp, type, key, dimensions, value)&lt;/code&gt;; fire-and-forget, appended to the log.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;query_metric(metric, time_range, group_by, filters)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;reads pre-aggregated rollups for a metric over a time range, grouped and filtered.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stream_metric(metric, filters)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;subscribes a live dashboard tile to the latest window as it closes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model separates the &lt;strong&gt;raw truth you can replay&lt;/strong&gt; from the &lt;strong&gt;rollups you actually serve&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;raw_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(event_time, type, key, dimensions, value)&lt;/code&gt; — the &lt;strong&gt;append-only log&lt;/strong&gt;, short retention; the replay fuel for reprocessing, not what dashboards read.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;rollup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(metric, key, window_start, granularity, measures)&lt;/code&gt; — the &lt;strong&gt;pre-aggregated&lt;/strong&gt; result per key per window (count, sum, HLL sketch, top-K); this is the served source of truth.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metric_definition&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;how a metric is derived from events: which field, which aggregation, which windows and granularities to materialise.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point to state: raw events are kept only long enough to replay and reprocess; the &lt;strong&gt;rollups are the served truth&lt;/strong&gt;, and they are what the storage layer is built to answer fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: ingest, process, store, serve
&lt;/h2&gt;

&lt;p&gt;Now the boxes. Events flow left to right through four stages, with a batch path on the side for exact numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Producers and ingestion collector:&lt;/strong&gt; clients, services and edge emit events to a thin collector that validates and forwards — it does no aggregation, just gets events onto the log fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion log (Kafka):&lt;/strong&gt; a durable, &lt;strong&gt;partitioned append-only log&lt;/strong&gt; is the buffer between producers and processing. It decouples the two, absorbs bursts, and is replayable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream processor (Flink / Spark Structured Streaming):&lt;/strong&gt; consumes the log partitions and computes &lt;strong&gt;windowed aggregates&lt;/strong&gt; per key — this is where the real work happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serving store (OLAP / time-series — Druid / Pinot / ClickHouse):&lt;/strong&gt; holds the &lt;strong&gt;pre-aggregated rollups&lt;/strong&gt; in a columnar, pre-rolled layout built for fast range and group-by queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query service and dashboards:&lt;/strong&gt; read rollups from the serving store; a live subscription pushes each window to dashboard tiles as it closes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch layer (optional, for exactness):&lt;/strong&gt; reprocesses the raw log on a schedule to produce exact numbers that reconcile the fast approximate layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharding:&lt;/strong&gt; everything partitions by &lt;strong&gt;key&lt;/strong&gt; — all events for a key land on one partition, so aggregation for that key is ordered and local.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that the design's whole job is to &lt;strong&gt;move the aggregation off the read path&lt;/strong&gt;: pre-aggregate as data arrives so a dashboard query is a lookup of a rollup, never a scan of raw events. Naming that — a partitioned log feeding a stream processor that rolls up into an OLAP store — is the signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx103200n5zwlckjd0yzc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx103200n5zwlckjd0yzc.jpeg" alt="A real-time analytics architecture diagram showing event producers writing to an ingestion collector, into a partitioned Kafka append log, consumed by a stream processor that computes windowed aggregates by key, which writes pre-aggregated rollups into an OLAP time-series store served to dashboards through a query service, with a separate batch layer reprocessing the raw log for exact numbers, everything partitioned by key." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: ingesting the event firehose
&lt;/h2&gt;

&lt;p&gt;Start at the front door, because if ingestion drops data the rest of the system is computing wrong numbers confidently. The follow-up is the spike: &lt;em&gt;traffic jumps ten times in a flash sale — what happens to the events you can't process fast enough?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is to put a &lt;strong&gt;durable, partitioned append log (Kafka)&lt;/strong&gt; between producers and the stream processor, never to let producers write straight into the aggregator:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decouple producers from consumers:&lt;/strong&gt; producers only have to append to the log, fast and fire-and-forget; the processor consumes at its own pace. Neither blocks the other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absorb bursts as lag, not loss:&lt;/strong&gt; when the firehose spikes past the processor's throughput, events pile up &lt;em&gt;in the log&lt;/em&gt; and the processor falls behind — &lt;strong&gt;back-pressure becomes consumer lag, not dropped data&lt;/strong&gt;. It catches up when the spike passes, because the data is safely durable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replayable:&lt;/strong&gt; because the log retains events with offsets, you can &lt;strong&gt;rewind and reprocess&lt;/strong&gt; — essential for fixing a bad aggregation, backfilling a new metric, or recovering a crashed processor from its last checkpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partition by key:&lt;/strong&gt; the log is split into partitions keyed by the aggregation key, so all events for a key land in one partition &lt;strong&gt;in order&lt;/strong&gt;, and the processor scales by assigning partitions across workers. Ordering is guaranteed &lt;em&gt;per partition&lt;/em&gt;, not globally — which is all windowed aggregation per key needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the log turns a scary, unbounded, bursty input into something the rest of the system can consume calmly and re-consume if it has to. A spike is not an outage; it is lag that drains.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Traffic spikes ten times in a flash sale — what happens to events you can't process fast enough?" Producers keep appending to the partitioned log, which is durable, so nothing is dropped — the stream processor simply falls behind and its consumer lag grows. When the spike passes, it catches up by reading the backlog; the dashboard is a few seconds staler for a while, but no event is lost and no count is wrong. The log absorbing the burst is exactly why I don't let producers write to the aggregator directly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Media &amp;amp; analytics · 60 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design a real-time analytics system out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: stream processing, windows and approximation
&lt;/h2&gt;

&lt;p&gt;Ingestion gets events in safely; this decides &lt;em&gt;what the numbers actually are&lt;/em&gt;. It is the heart of the round and the richest source of follow-ups, because it is where correctness lives. The follow-up is the one that separates memorised answers from real ones: &lt;em&gt;an event arrives two minutes late, after its window already closed — what happens?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The processor computes &lt;strong&gt;windowed aggregates&lt;/strong&gt; — a metric per key over a bounded slice of time — and three decisions make them correct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tumbling vs sliding windows:&lt;/strong&gt; &lt;strong&gt;tumbling&lt;/strong&gt; windows are fixed and non-overlapping (every minute, a fresh bucket) — the default for dashboards; &lt;strong&gt;sliding&lt;/strong&gt; windows overlap (a trailing five-minute count updated every minute) when you need a smooth moving metric. Say which and why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event time, not processing time:&lt;/strong&gt; bucket each event by &lt;strong&gt;when it actually happened&lt;/strong&gt; (its event timestamp), not when it reached the processor. A mobile client offline for a minute sends events whose event time is a minute old; counting them by arrival time would put them in the wrong window and make every historical number drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watermarks close windows:&lt;/strong&gt; since you aggregate by event time but events arrive out of order, you need a signal to decide a window is &lt;em&gt;done&lt;/em&gt;. A &lt;strong&gt;watermark&lt;/strong&gt; is a heuristic — "I believe I've now seen all events with timestamps up to time T" — that lets the processor close a window and emit its aggregate. It is a deliberate trade between waiting longer (more correct) and emitting sooner (fresher).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Late events past the watermark:&lt;/strong&gt; an event that lands after its window closed is handled by an explicit policy you must name — &lt;strong&gt;drop&lt;/strong&gt; it (fine for a live dashboard), route it to a &lt;strong&gt;side output&lt;/strong&gt; for later reconciliation, or &lt;strong&gt;re-open and correct&lt;/strong&gt; the window (needed for exact numbers). Never pretend late events don't happen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then the scale problem. At a billion events a minute, some aggregates &lt;strong&gt;won't fit in memory if computed exactly&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Approximate distinct counts (HyperLogLog):&lt;/strong&gt; counting unique users exactly means holding every id you've seen — gigabytes per window. A &lt;strong&gt;HyperLogLog&lt;/strong&gt; sketch estimates cardinality in &lt;strong&gt;a couple of kilobytes&lt;/strong&gt; with a small bounded error (~2%), and sketches &lt;strong&gt;merge&lt;/strong&gt;, so per-partition sketches combine into a global count. For a live "unique users" tile, that trade is almost always right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy hitters (count-min sketch):&lt;/strong&gt; to find the top-K keys (hottest pages, biggest spenders) without a per-key counter for millions of keys, a &lt;strong&gt;count-min sketch&lt;/strong&gt; estimates frequencies in fixed memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit rollups:&lt;/strong&gt; the processor writes &lt;strong&gt;pre-aggregated rollups&lt;/strong&gt; — &lt;code&gt;(metric, key, window, measures)&lt;/code&gt; — downstream, so the serving store never sees raw events.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sentence that shows range: &lt;em&gt;for a live dashboard I'll accept a HyperLogLog estimate within a couple of percent because freshness matters more than the last digit; if the same number feeds billing, I run a batch job over the raw log to get it exact and reconcile.&lt;/em&gt; That is the freshness-versus-correctness trade the round is testing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"An event arrives two minutes late, after its window already closed — what happens?" It depends on the policy I chose, and I'd state it: aggregate by event time, use a watermark to decide a window is complete, and for anything past the watermark either drop it (acceptable for a live dashboard), send it to a side output to reconcile later, or re-open the window and emit a correction if the metric must be exact. The watermark is a tunable trade between freshness and completeness — I don't pretend events arrive in order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftld6d13f5ymr20txmi59.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftld6d13f5ymr20txmi59.jpeg" alt="A diagram of windowed stream aggregation showing events bucketed by event time into tumbling windows, a watermark line marking how far time has advanced so a window can close and emit its aggregate, a late event arriving after the watermark routed to a side output, and a HyperLogLog sketch computing an approximate distinct-user count in kilobytes instead of storing every id." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Media &amp;amp; analytics · 60 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend event-time windows and watermarks out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: storage, serving and correctness (lambda vs kappa)
&lt;/h2&gt;

&lt;p&gt;Aggregation produces the numbers; this decides how a dashboard reads them in under a second over a huge history, and how you keep them correct. The follow-up is the one about scale on the read side: &lt;em&gt;the dashboard must render in under a second over a trillion rows — how?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The serving layer is an &lt;strong&gt;OLAP / time-series store&lt;/strong&gt; (Druid, Pinot or ClickHouse), not a general-purpose transactional database, and the reasons are specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pre-aggregated and columnar:&lt;/strong&gt; it stores the &lt;strong&gt;rollups&lt;/strong&gt;, already grouped by key and window, in a columnar layout, so a range + group-by query touches only the relevant columns and segments — not a trillion raw rows. The expensive work was done once on the write path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-partitioned, hot and cold:&lt;/strong&gt; data is partitioned by time, so a query for "last hour" scans a tiny recent segment; older data is rolled up to a &lt;strong&gt;coarser granularity&lt;/strong&gt; (minutes → hours → days) and eventually expired, keeping the hot set small and fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built for this access pattern:&lt;/strong&gt; these stores are tuned exactly for "aggregate a metric across a time range, grouped by a dimension," which is every analytics dashboard query, so they answer sub-second where a row store would table-scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then correctness at the architecture level — name the two shapes and pick one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lambda architecture:&lt;/strong&gt; run a &lt;strong&gt;speed layer&lt;/strong&gt; (the stream pipeline — fast, approximate) &lt;em&gt;beside&lt;/em&gt; a &lt;strong&gt;batch layer&lt;/strong&gt; (reprocesses the raw log periodically for exact numbers), and &lt;strong&gt;merge them at query time&lt;/strong&gt; (recent from the speed layer, settled history from batch). It gives you both freshness and exactness, at the cost of &lt;strong&gt;two codebases&lt;/strong&gt; computing the same logic twice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kappa architecture:&lt;/strong&gt; keep &lt;strong&gt;one streaming pipeline&lt;/strong&gt; and, when you need to reprocess (a bug, a new metric, a correction), &lt;strong&gt;replay the log&lt;/strong&gt; through it rather than maintaining a separate batch system. Simpler and less duplication; it leans on the log being replayable, which it is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, &lt;strong&gt;delivery semantics&lt;/strong&gt;, because duplicates corrupt counts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exactly-once or effectively-once:&lt;/strong&gt; the processor's &lt;strong&gt;checkpointing&lt;/strong&gt; can give exactly-once within the pipeline; where it can't, use &lt;strong&gt;at-least-once delivery plus idempotent writes&lt;/strong&gt; — an &lt;strong&gt;upsert keyed by &lt;code&gt;(metric, key, window)&lt;/code&gt;&lt;/strong&gt; — so re-processing the same event or replaying after a crash &lt;strong&gt;overwrites rather than adds&lt;/strong&gt;, and a duplicate never double-counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the read path is fast because all the aggregation already happened on the write path, and the system stays correct because the raw log is replayable and the writes are idempotent — a crash or a correction is just a reprocess, not a lost or doubled number.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2subazrjrxsxlqbmsx62.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2subazrjrxsxlqbmsx62.jpeg" alt="A diagram comparing the lambda and kappa architectures for serving real-time analytics: the lambda side runs a fast approximate speed layer beside a slower exact batch layer that reprocesses the raw log, merged at query time, while the kappa side runs a single streaming pipeline and reprocesses by replaying the log, both writing rollups into an OLAP store that answers a dashboard range-and-group-by query in under a second." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the real-time analytics round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Why not just run a &lt;code&gt;GROUP BY&lt;/code&gt; over the raw events when the dashboard loads?"&lt;/strong&gt; → Because at a billion events a minute that scan is impossibly slow and expensive per load. You pre-aggregate the stream as it arrives and serve cheap rollups; the read becomes a lookup, not a scan. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Exact or approximate counts?"&lt;/strong&gt; → Approximate for live dashboards — HyperLogLog gives a distinct count within a couple of percent in kilobytes, which beats storing every id; exact only where it must be (billing), computed by a batch job over the raw log and reconciled. State the freshness-versus-correctness trade. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"An event arrives late — what happens?"&lt;/strong&gt; → Aggregate by event time, close windows on a watermark, and for anything past it apply a stated policy: drop, side-output to reconcile, or re-open and correct. The watermark trades freshness against completeness. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The dashboard must render sub-second over a trillion rows — how?"&lt;/strong&gt; → Serve pre-aggregated, time-partitioned, columnar rollups from an OLAP store; the aggregation already happened on the write path, so the query touches tiny recent segments, not raw rows. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How do you keep numbers correct across crashes and reprocessing?"&lt;/strong&gt; → At-least-once plus idempotent upserts keyed by &lt;code&gt;(metric, key, window)&lt;/code&gt; so duplicates never double-count, a replayable log so reprocessing is a rewind, and lambda or kappa to reconcile the exact numbers. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do the real-time analytics system end to end on paper — requirements, estimate, API, data model, and the ingest/process/store/serve boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill the aggregation core: tumbling vs sliding windows, event time vs processing time, watermarks and late-event policy, and HyperLogLog vs exact counting. Say each tradeoff in one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own serving and correctness — why an OLAP store answers sub-second, lambda vs kappa, and at-least-once plus idempotent writes. Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 60-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the real-time analytics pipeline is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design a real-time analytics system step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (ingest a high-volume event stream, compute windowed aggregate metrics per key, serve live dashboards and ad-hoc queries with seconds-level freshness, and decide per metric whether approximate-and-fast or exact-and-delayed is needed); estimate to show the load is write/aggregate-heavy and read-light, that raw retention is huge so you roll up and expire raw, and that the unit of work is the &lt;code&gt;(key, window)&lt;/code&gt; tuple; define the small API (ingest_event, query_metric, stream_metric) over a raw-event log plus a rollup model; draw the design (producers → partitioned Kafka log → stream processor → OLAP serving store → query service, with a batch layer for exact numbers); then deep-dive ingestion, stream processing with windows and approximation, and storage/serving with lambda vs kappa. Finish on the follow-ups about late events and sub-second reads.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between stream processing and batch processing here?
&lt;/h3&gt;

&lt;p&gt;Stream processing aggregates events continuously the moment they arrive, so metrics are fresh within seconds but are computed on incomplete, out-of-order data and often use approximations. Batch processing reprocesses a complete, bounded set of raw events on a schedule, so it is slower but can produce exact numbers and backfill new metrics over all of history. A real-time analytics system usually needs both: the stream pipeline for the live dashboard, and a batch pass (or a replay through the same pipeline) to settle exact figures. The lambda architecture runs them side by side and merges at query time; the kappa architecture keeps one streaming pipeline and reprocesses by replaying the log.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a watermark, and how are late events handled?
&lt;/h3&gt;

&lt;p&gt;A watermark is the stream processor's heuristic estimate that it has now seen all events with event timestamps up to some time T, which lets it close a time window and emit that window's aggregate. Because you bucket events by when they happened (event time) rather than when they arrived, and because events arrive out of order, you need this signal to decide a window is complete. An event that arrives after its window's watermark has passed is a late event, and you handle it with an explicit policy: drop it (acceptable for a live dashboard), route it to a side output to reconcile later, or re-open the window and emit a correction (for metrics that must be exact). The watermark is a tunable trade-off between freshness (emit sooner) and completeness (wait longer).&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you count unique users across billions of events without running out of memory?
&lt;/h3&gt;

&lt;p&gt;Use a probabilistic data structure instead of storing every id. A HyperLogLog sketch estimates the number of distinct values (cardinality) using only a couple of kilobytes per window, with a small bounded error of roughly two percent, where an exact set would need memory proportional to the number of unique users — gigabytes at this scale. HyperLogLog sketches also merge, so each partition computes its own sketch and they combine into a global distinct count. For finding the heaviest keys (top-K hot pages or users), a count-min sketch estimates frequencies in fixed memory. You accept a small, bounded approximation for live metrics because freshness matters more than the last digit; where an exact number is required, a batch job over the raw log produces it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between the lambda and kappa architectures?
&lt;/h3&gt;

&lt;p&gt;Both aim to serve fresh metrics while still being able to produce exact, corrected numbers. The lambda architecture runs two layers: a speed layer (the real-time stream pipeline, fast but approximate) and a batch layer (reprocesses the raw log for exact results), merged at query time — powerful but it means maintaining two codebases that implement the same aggregation logic twice. The kappa architecture keeps a single streaming pipeline and, whenever it needs to reprocess for a correction or a new metric, replays the retained log through that one pipeline — simpler, with no duplicated logic, relying on the log being durable and replayable. Pick kappa for simplicity when one pipeline can meet the latency and correctness needs; pick lambda when a separate heavy batch computation genuinely earns its keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what a watermark is. The round is won on defending the trade-offs out loud.&lt;/h3&gt;

&lt;p&gt;Pick the Real-Time Analytics round, draw the ingest-process-store-serve pipeline on the canvas, and defend it out loud to a voice interviewer that keeps asking "exact or approximate — and why?" and "an event arrives late — what happens?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the Real-Time Analytics round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>dataengineering</category>
      <category>walkin</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Design a web search engine: the system design interview walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:47:46 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-a-web-search-engine-the-system-design-interview-walkthrough-pef</link>
      <guid>https://dev.to/gowthampotureddi/design-a-web-search-engine-the-system-design-interview-walkthrough-pef</guid>
      <description>&lt;h2&gt;
  
  
  Design a web search engine: the system design interview walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design a web search engine&lt;/strong&gt; sounds impossibly large — "build Google" — and that size is exactly the trap. The sentence hides three separate systems: a &lt;strong&gt;crawler&lt;/strong&gt; that discovers pages, an &lt;strong&gt;indexer&lt;/strong&gt; that turns them into something searchable, and a &lt;strong&gt;serving&lt;/strong&gt; layer that answers a query in the blink of an eye. A strong candidate says that out loud in the first minute, scopes the crawler to its own problem, and names what this round is really about: given a corpus that has already been crawled, how do you answer a text query against &lt;strong&gt;billions of documents in well under a second&lt;/strong&gt; and return the &lt;em&gt;best&lt;/em&gt; results, not just the matching ones. That reframe — &lt;strong&gt;search engine system design&lt;/strong&gt; is a top-K retrieval problem, not a crawling problem — is the move that turns an unanswerable prompt into a 45-minute design.&lt;/p&gt;

&lt;p&gt;Underneath the reframe is one data structure and one hard operation. The data structure is the &lt;strong&gt;inverted index&lt;/strong&gt;: instead of asking "what words does this document contain," you flip it to "what documents contain this word," so a query becomes a lookup, not a scan. The hard operation is doing that lookup across an index far too big for one machine, which forces you to &lt;strong&gt;shard by document&lt;/strong&gt; and answer every query as a &lt;strong&gt;scatter-gather&lt;/strong&gt; — fan the query out to every shard in parallel, let each return its local best, and merge. Get that spine — an offline pipeline builds an inverted index, the index is sharded by document and queried by scatter-gather, and a ranking function decides the order — and the rest of the &lt;strong&gt;system design interview&lt;/strong&gt; is deciding how the index is built, how the fan-out stays fast, and how you rank two matching pages against each other. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqxuw4lpvsekhf28qlv4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnqxuw4lpvsekhf28qlv4.jpeg" alt="Title card for a system design interview walkthrough on designing a web search engine, showing a text query fanned out to many document-shard index servers in parallel, each returning its local top results into a coordinator that merges them into one ranked list, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the web search round is actually run
&lt;/h2&gt;

&lt;p&gt;A web search 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 is so obviously too big that it immediately tests whether you can &lt;strong&gt;scope&lt;/strong&gt;: the candidates who try to design the crawler, the ranking machine-learning model, and the serving layer all at once run out of clock with nothing finished. The ones who score say "three systems; I'll scope the crawler out and design indexing and serving," and spend their minutes where the depth is.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "I'd build an index and search it" stand. "Your corpus is a hundred billion documents and the index is tens of terabytes — it cannot live on one machine. How do you split it, and how does a single query still come back in under a second?" That question — retrieval at a scale no single node can hold — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a prompt too big to take literally, and a follow-up after every decision you make.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — corpus size and query volume, to justify why the index must be sharded and the query fanned out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: the offline indexing pipeline, the document-sharded query servers, the coordinator, and ranking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe it as top-K retrieval over an inverted index and scope the crawler out), &lt;strong&gt;communication&lt;/strong&gt; (did you say in one sentence why document-sharding beats term-sharding, and why retrieval and ranking are two separate phases), &lt;strong&gt;technical execution&lt;/strong&gt; (an offline pipeline producing an inverted index, document shards with replicas, a coordinator merging per-shard top-K, and a ranker), and &lt;strong&gt;time management&lt;/strong&gt; (did you spend your 45 minutes on the index, serving and ranking, not on the crawler or the search box UI). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what a web search engine must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system — and for this prompt, scoping &lt;em&gt;is&lt;/em&gt; the first scored move.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accept a free-text query and return a &lt;strong&gt;ranked&lt;/strong&gt; list of relevant documents (url, title, snippet); support multi-term queries and phrase queries; results should be reasonably fresh.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spelling correction, autocomplete, personalisation, image/news verticals, pagination — name them, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Out of scope (say it)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;crawler&lt;/strong&gt; that discovers and fetches pages is its own system — assume a crawled corpus exists.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Low latency&lt;/strong&gt; — sub-second response — at very &lt;strong&gt;high QPS&lt;/strong&gt;; scale to &lt;strong&gt;billions of documents&lt;/strong&gt;; &lt;strong&gt;relevance&lt;/strong&gt; quality (the right results near the top); high availability; freshness within a tolerable lag.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"This is really three systems — crawl, index, serve — and this round is the serve half. Assume a crawled corpus; I'll design an inverted index, document-sharded query serving, and ranking, and treat the crawler as a separate problem."&lt;/em&gt; Say that and you have framed the entire design — and shown judgement — before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving two things: &lt;strong&gt;the index is far too big for one machine&lt;/strong&gt;, and &lt;strong&gt;the query rate is far too high to recompute anything per request&lt;/strong&gt; — so the index must be partitioned and the popular results cached.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Corpus size&lt;/td&gt;
&lt;td&gt;billions of documents, tens of terabytes of index&lt;/td&gt;
&lt;td&gt;the index &lt;strong&gt;cannot fit on one machine&lt;/strong&gt; — it must be &lt;strong&gt;sharded by document&lt;/strong&gt; across many servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query volume&lt;/td&gt;
&lt;td&gt;tens of thousands of queries per second, with a &lt;strong&gt;heavy-tailed&lt;/strong&gt; distribution (a few queries are wildly popular)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;replicas&lt;/strong&gt; per shard for throughput, and a &lt;strong&gt;query cache&lt;/strong&gt; for the popular tail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Per-query work&lt;/td&gt;
&lt;td&gt;each query touches &lt;strong&gt;every shard&lt;/strong&gt; to find its best local results&lt;/td&gt;
&lt;td&gt;fan-out must be &lt;strong&gt;parallel&lt;/strong&gt;; the &lt;strong&gt;slowest shard sets the tail latency&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Freshness&lt;/td&gt;
&lt;td&gt;the web changes constantly, but not every page every second&lt;/td&gt;
&lt;td&gt;build the index &lt;strong&gt;offline in batch&lt;/strong&gt;, add &lt;strong&gt;incremental segments&lt;/strong&gt; for new/changed pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, there are &lt;strong&gt;two regimes&lt;/strong&gt; that barely touch: &lt;strong&gt;offline indexing&lt;/strong&gt; is a throughput problem (process the whole corpus into an index), while &lt;strong&gt;online serving&lt;/strong&gt; is a latency problem (answer one query fast) — design them separately. Second, because no machine can hold the index and no request can afford a scan, the system is defined by &lt;strong&gt;partitioning the index by document&lt;/strong&gt; and &lt;strong&gt;fanning each query out in parallel&lt;/strong&gt; to all partitions. You have now pointed the interview at the real deep dives — building the index, serving the query across shards, and ranking — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The external API is tiny — one endpoint. The interesting model is internal: the inverted index and the document store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;search(query, page)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;parse the query, retrieve matching documents, rank them, and return a page of results (url, title, snippet).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_document(doc_id)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;internal lookup of a document's metadata for rendering a result.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model has three parts. The &lt;strong&gt;inverted index&lt;/strong&gt; is the heart; the &lt;strong&gt;document store&lt;/strong&gt; holds what you show the user; the &lt;strong&gt;query cache&lt;/strong&gt; is an optimisation:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inverted_index&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;term -&amp;gt; posting list&lt;/code&gt;, where each &lt;strong&gt;posting&lt;/strong&gt; is &lt;code&gt;(doc_id, positions, term_frequency)&lt;/code&gt;. Positions let you answer &lt;strong&gt;phrase/proximity&lt;/strong&gt; queries; term frequency feeds ranking. This is &lt;strong&gt;immutable&lt;/strong&gt; once built — new data arrives as new segments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;document&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;doc_id -&amp;gt; (url, title, snippet, authority_score, length, last_crawled)&lt;/code&gt; — the metadata you render and the query-independent signals you rank with.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;query_cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;normalised_query -&amp;gt; ranked result page&lt;/code&gt;, aged against a freshness budget — absorbs the popular tail of queries.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: indexing pipeline, query serving and ranking
&lt;/h2&gt;

&lt;p&gt;Now the boxes. Split the picture into the &lt;strong&gt;offline&lt;/strong&gt; path that builds the index and the &lt;strong&gt;online&lt;/strong&gt; path that serves a query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Indexing pipeline (offline, batch):&lt;/strong&gt; reads the crawled corpus, &lt;strong&gt;tokenises and normalises&lt;/strong&gt; each document, and builds the &lt;strong&gt;inverted index&lt;/strong&gt;. It is a throughput job (MapReduce-style) that produces an &lt;strong&gt;immutable index&lt;/strong&gt; shipped out to the query servers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query servers, sharded by document:&lt;/strong&gt; the index is split into shards, each holding the &lt;strong&gt;full inverted index over a slice of the corpus&lt;/strong&gt;. Every shard is &lt;strong&gt;replicated&lt;/strong&gt; for throughput and availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coordinator / aggregator:&lt;/strong&gt; receives the parsed query, &lt;strong&gt;scatter-gathers&lt;/strong&gt; it to every shard in parallel, collects each shard's &lt;strong&gt;local top-K&lt;/strong&gt;, and &lt;strong&gt;merges&lt;/strong&gt; them into a global top-K.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranking:&lt;/strong&gt; a cheap retrieve inside each shard, then an expensive &lt;strong&gt;rerank&lt;/strong&gt; of the merged candidates using text relevance plus query-independent authority.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query cache:&lt;/strong&gt; sits in front of the coordinator and returns the stored ranked page for popular queries without touching the shards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document store:&lt;/strong&gt; holds url/title/snippet/authority for rendering results and for ranking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that the &lt;strong&gt;document is the unit of partitioning&lt;/strong&gt;: each shard can answer a whole query over its slice by itself, so the coordinator never ships posting lists between nodes — it only merges small top-K lists. Naming that — an offline-built inverted index, document-sharded and queried by scatter-gather, then reranked and cached — is a signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr615ahurlq7fmsmccu33.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr615ahurlq7fmsmccu33.jpeg" alt="A web search engine architecture diagram showing an offline indexing pipeline that tokenises and normalises crawled documents into an immutable inverted index shipped to document-sharded query servers with replicas, and an online path where a query hits a parser then a coordinator that scatter-gathers to every shard, each returning its local top-K, which the coordinator merges and reranks before a query cache returns the ranked results, with a separate document store for titles and snippets." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: building the inverted index
&lt;/h2&gt;

&lt;p&gt;This is the structure the whole system is built on, so expect the interviewer to make you build it from a raw document. The follow-up is concrete: &lt;em&gt;you have the text of a page — walk me from that to something a query can hit in milliseconds.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is a pipeline that flips documents into terms. For each crawled document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokenise:&lt;/strong&gt; split the text into terms (words), handling punctuation and case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalise:&lt;/strong&gt; lowercase, &lt;strong&gt;stem&lt;/strong&gt; (so "running" and "run" match), and drop very common &lt;strong&gt;stopwords&lt;/strong&gt; — so queries match regardless of surface form.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit postings:&lt;/strong&gt; for each term, record a posting &lt;code&gt;(doc_id, positions, term_frequency)&lt;/code&gt;. Positions are what later let you answer a &lt;strong&gt;phrase query&lt;/strong&gt; ("brown fox" as an ordered pair, not two separate words).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group by term:&lt;/strong&gt; invert and collect every posting for a term into its &lt;strong&gt;posting list&lt;/strong&gt;. The result is the inverted index: &lt;code&gt;term -&amp;gt; [posting, posting, ...]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two properties decide how you operate it. First, this is an &lt;strong&gt;offline batch job&lt;/strong&gt; — a throughput problem over the whole corpus, naturally a &lt;strong&gt;MapReduce-style&lt;/strong&gt; map (document to postings) and reduce (group by term). Second, the built index is treated as &lt;strong&gt;immutable&lt;/strong&gt;: you do not edit posting lists in place under live queries. Freshness comes from building &lt;strong&gt;incremental segments&lt;/strong&gt; — small indexes over new or changed pages — and &lt;strong&gt;merging&lt;/strong&gt; them into the query path, so a query reads the main index plus the recent segments. State the trade-off plainly: a bigger batch cadence is cheaper but staler; incremental segments buy freshness at the cost of more merge work and more segments to search.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do you keep the index fresh without rebuilding it from scratch every time a page changes?" You don't rebuild. The main index is immutable and built in batch; new and changed pages are indexed into small incremental segments that are merged into the serving path, so a query reads the big index plus a handful of recent segments. Periodically the segments are compacted back into the main index. Freshness is a cadence-versus-cost dial, not a live edit.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2lixqfuj6xguohr6quuw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2lixqfuj6xguohr6quuw.jpeg" alt="A diagram of building an inverted index, showing three crawled documents whose text is tokenised and normalised into terms, each term emitting a posting of the document id and positions, then grouped by term into posting lists so that a term maps to the list of documents that contain it, produced by an offline batch pipeline as an immutable index with incremental segments merged in for freshness." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Search &amp;amp; discovery · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Build the inverted index out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: query serving at scale (document sharding and scatter-gather)
&lt;/h2&gt;

&lt;p&gt;This is the decision the scale follow-up forces. The probe: &lt;em&gt;the index is sharded across a thousand machines — how does one query use all of them and still return in under a second?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start with how a single query works on one index. Parse it into terms; for each term, fetch its &lt;strong&gt;posting list&lt;/strong&gt;; then &lt;strong&gt;intersect&lt;/strong&gt; the lists (for an AND query, keep only documents that appear in every term's list) to get the candidate set. Now scale it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shard by document:&lt;/strong&gt; split the corpus into slices and give each shard the &lt;strong&gt;full inverted index over its slice&lt;/strong&gt;. Crucially, a shard can answer the &lt;em&gt;whole&lt;/em&gt; multi-term query over its own documents with no help from other shards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scatter-gather:&lt;/strong&gt; the coordinator sends the query to &lt;strong&gt;every shard in parallel&lt;/strong&gt;. Each shard retrieves its candidates, ranks them locally, and returns only its &lt;strong&gt;local top-K&lt;/strong&gt; (say, its best 100). The coordinator &lt;strong&gt;merges&lt;/strong&gt; the per-shard top-Ks into the global top-K.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replicate each shard:&lt;/strong&gt; multiple replicas per shard serve the QPS and provide availability; the coordinator can send &lt;strong&gt;hedged requests&lt;/strong&gt; so one slow replica doesn't drag the whole query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why document-sharding, not term-sharding:&lt;/strong&gt; if you instead split by &lt;em&gt;term&lt;/em&gt; (each node owns some terms' posting lists), a multi-term query must ship huge posting lists between nodes to intersect them, and popular terms turn their node into a hotspot. Document-sharding keeps every query &lt;strong&gt;local to each shard&lt;/strong&gt;, makes fan-out uniform, and lets you grow by simply adding shards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: the coordinator never moves posting lists around — it only fans out the query and merges small top-K lists, so the network cost per query is tiny and constant. The latency is set by the &lt;strong&gt;slowest shard&lt;/strong&gt;, which is why replicas and hedging matter. This is the same scatter-gather shape behind most large search and analytics systems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A query has to touch every shard — how is that fast?" Because every shard works in parallel and each only returns its local top-K — a few hundred bytes — not raw posting lists. The coordinator fans out once, waits for the shards (hedging around slow replicas), and merges small sorted lists into the global top results. Fan-out is parallel, the per-shard work is bounded by top-K, and the network payload is tiny; the tail latency is the slowest shard, so we replicate and hedge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fld6ri81rotjnqlzo7h4p.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fld6ri81rotjnqlzo7h4p.jpeg" alt="A scatter-gather diagram comparing document sharding and term sharding, showing a multi-term query fanned out in parallel to every document shard where each shard holds the full inverted index over a slice of the corpus and answers the whole query locally returning its local top-K, which a coordinator merges into a global top-K, contrasted with term sharding where posting lists for different terms live on different nodes and must be shipped across the network to intersect." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Search &amp;amp; discovery · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend the scatter-gather sharding out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: ranking, relevance and caching
&lt;/h2&gt;

&lt;p&gt;Retrieval finds the documents that &lt;em&gt;match&lt;/em&gt;; ranking decides which comes &lt;em&gt;first&lt;/em&gt; — and this is where "which one is at the top, and why" is decided. The follow-up is the comparison: &lt;em&gt;two pages both contain every query word — how do you order them?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is a score that blends what the query says with what the corpus and the web say about each document:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text relevance (query-dependent):&lt;/strong&gt; a function of how well the document matches the query terms — &lt;strong&gt;TF-IDF&lt;/strong&gt; or &lt;strong&gt;BM25&lt;/strong&gt;: a term counts for more when it appears often &lt;em&gt;in this document&lt;/em&gt; (term frequency) and is &lt;em&gt;rare across the corpus&lt;/em&gt; (inverse document frequency), with BM25 adding length normalisation so long documents don't win by sheer size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authority and quality (query-independent):&lt;/strong&gt; signals computed ahead of time — a &lt;strong&gt;link-based authority score&lt;/strong&gt; (the &lt;strong&gt;PageRank&lt;/strong&gt;-style idea that a page linked to by many good pages is itself good), freshness, and quality signals. These are stored per document and don't depend on the query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two-phase retrieve-then-rerank:&lt;/strong&gt; each shard does a &lt;strong&gt;cheap&lt;/strong&gt; retrieve to a few thousand candidates using a simple score; the coordinator then &lt;strong&gt;reranks&lt;/strong&gt; the merged top candidates with the &lt;strong&gt;expensive&lt;/strong&gt; full feature set (including authority and, in a real system, learned ranking). You only pay the expensive model on a small, promising set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching:&lt;/strong&gt; because the query distribution is heavy-tailed, &lt;strong&gt;cache the ranked results&lt;/strong&gt; of popular queries so they skip the shards entirely. Age the cache against a &lt;strong&gt;freshness budget&lt;/strong&gt; — the trade-off is serving a slightly stale top result cheaply versus recomputing for freshness.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: &lt;strong&gt;matching and ordering are two different problems&lt;/strong&gt;. Retrieval is about correctness (did we find every candidate) and is cheap and parallel; ranking is about quality (is the best one first) and is where the expensive signals live, applied late to a small set. Say that split out loud and the follow-up about ordering answers itself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Two documents both match every word — which one is first, and why?" The one with the higher combined score. Text relevance (BM25 — the query terms are frequent in the document and rare across the corpus, normalised for length) says how well it matches; query-independent authority (a PageRank-like link score) and freshness say how much to trust it. We retrieve cheaply to a few thousand candidates per shard, merge, then rerank the top of that merged set with the full, expensive signals — so the ordering reflects both the match and the document's standing, without scoring billions of pages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the web search round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Isn't this just building Google? Where do you start?"&lt;/strong&gt; → It's three systems — crawl, index, serve. Scope the crawler out as its own problem and design the serve half: an inverted index, built offline, sharded by document, queried by scatter-gather, then ranked. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The index is tens of terabytes — how do you split it?"&lt;/strong&gt; → Shard by document: each shard holds the full inverted index over a slice of the corpus, so it can answer any query locally; replicate each shard for QPS and availability. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A query touches every shard — how is that fast?"&lt;/strong&gt; → Parallel scatter-gather; each shard returns only its local top-K, and the coordinator merges small sorted lists — tiny network payload. Tail latency is the slowest shard, so we hedge across replicas. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why document sharding and not term sharding?"&lt;/strong&gt; → Term sharding forces multi-term queries to ship big posting lists between nodes and turns popular terms into hotspots; document sharding keeps every query local to each shard and scales by adding shards. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Two pages both match — which ranks first?"&lt;/strong&gt; → The higher combined score: BM25 text relevance plus query-independent authority (PageRank-like) and freshness, applied as a cheap retrieve then an expensive rerank of the merged top candidates. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do the web search engine end to end on paper — requirements (scope the crawler out), estimate, API, data model, and the indexing/serving/ranking boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill the inverted index: tokenise, normalise, postings with positions, offline batch build, immutable index plus incremental segments for freshness. Say each step in one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own serving and ranking — document sharding, scatter-gather with per-shard top-K, why document beats term sharding, and BM25 + authority with two-phase retrieve-then-rerank and a query cache. Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the web search engine is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design a web search engine step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (accept a text query and return a ranked list of relevant documents with url, title and snippet, support multi-term and phrase queries, stay reasonably fresh — and explicitly scope the crawler out as a separate system); estimate to show the index is far too big for one machine and the query rate too high to recompute per request, so the index is sharded by document and popular results are cached; define the small API (search(query, page)) over an inverted-index and document-store model; draw the design (an offline indexing pipeline building an immutable inverted index, document-sharded query servers with replicas, a coordinator that scatter-gathers and merges per-shard top-K, a ranking phase, and a query cache); then deep-dive building the inverted index, query serving across shards, and ranking. Finish on the follow-ups about scale, fan-out and ordering.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is an inverted index?
&lt;/h3&gt;

&lt;p&gt;An inverted index is the core data structure of a search engine. Instead of mapping each document to the words it contains, it maps each word (term) to the list of documents that contain it — the term's posting list, where each posting records a document id, the positions of the term in that document, and its term frequency. Positions let the engine answer phrase and proximity queries; term frequency feeds ranking. It is called "inverted" because it flips the natural document-to-terms relationship into terms-to-documents, which turns a query into a fast lookup and intersection of posting lists instead of a scan of every document.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a search engine serve a query across billions of documents?
&lt;/h3&gt;

&lt;p&gt;It shards the index by document — each shard holds the full inverted index over a slice of the corpus — and answers every query as a scatter-gather. A coordinator parses the query, fans it out to all shards in parallel, and each shard retrieves and ranks its own candidates and returns only its local top-K. The coordinator merges those small per-shard top-K lists into the global top results, reranks them with the full scoring signals, and returns a page. Each shard is replicated for throughput and availability, and the coordinator hedges requests so one slow replica doesn't set the latency. Popular queries are served from a result cache without touching the shards at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Document sharding vs term sharding — which and why?
&lt;/h3&gt;

&lt;p&gt;Document sharding. If you split the index by document, every shard can answer a whole multi-term query over its own slice locally, so the coordinator only merges small top-K lists and never ships posting lists between machines; fan-out is uniform and you scale by adding shards. If you split by term instead (each node owns some terms' posting lists), a multi-term query has to transfer large posting lists across the network to intersect them, and a popular term turns its node into a hotspot. Term partitioning can save some disk I/O for single-term queries, but for a general multi-term web search, document sharding is the standard choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  How are search results ranked?
&lt;/h3&gt;

&lt;p&gt;By a score that combines query-dependent and query-independent signals. The query-dependent part is text relevance — TF-IDF or BM25 — which rewards query terms that are frequent in the document and rare across the corpus, with length normalisation. The query-independent part is computed ahead of time per document: a link-based authority score (the PageRank idea that a page linked by many good pages is trustworthy), freshness, and quality signals. Serving is two-phase: each shard cheaply retrieves a few thousand candidates, the coordinator merges them, and only then does an expensive rerank with the full feature set run on the small merged set — so the best result rises to the top without scoring billions of pages per query.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what an inverted index is. The round is won on defending the scatter-gather out loud.&lt;/h3&gt;

&lt;p&gt;Pick the Web Search Engine round, draw the indexing pipeline, document-sharded serving and ranking on the canvas, and defend it out loud to a voice interviewer that keeps asking "the index is a hundred billion documents — how do you shard it?" and "two pages both match — which is first, and why?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the Web Search Engine round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>interview</category>
      <category>walkin</category>
      <category>dataengineering</category>
      <category>designsystem</category>
    </item>
    <item>
      <title>Design a CDN: the content delivery network system design walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:42:55 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-a-cdn-the-content-delivery-network-system-design-walkthrough-2nhm</link>
      <guid>https://dev.to/gowthampotureddi/design-a-cdn-the-content-delivery-network-system-design-walkthrough-2nhm</guid>
      <description>&lt;h2&gt;
  
  
  Design a CDN: the content delivery network system design walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design a CDN&lt;/strong&gt; sounds like a one-liner — put the files closer to the users — and that one line hides the whole interview. A &lt;strong&gt;content delivery network system design&lt;/strong&gt; is not a file server with extra machines; it is a &lt;strong&gt;globally distributed read-through cache&lt;/strong&gt; 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: &lt;strong&gt;how a client is routed to the nearest healthy edge&lt;/strong&gt;, and &lt;strong&gt;how a cached object stays correct — fresh, and invalidated the instant it changes — without a flood of misses stampeding the one origin behind it&lt;/strong&gt;. 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 &lt;strong&gt;caching&lt;/strong&gt; problem and the geography problem are the same problem.&lt;/p&gt;

&lt;p&gt;That reframe is the spine of the round. "Serve content faster" becomes &lt;em&gt;answer as close to the user as possible, on a cache hit, and make the rare miss both cheap and safe for the origin.&lt;/em&gt; Everything after it follows: you spread &lt;strong&gt;edge servers&lt;/strong&gt; across &lt;strong&gt;points of presence&lt;/strong&gt; 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 &lt;strong&gt;system design interview&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjgze53j9245y8qvwyqr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnjgze53j9245y8qvwyqr.jpeg" alt="Title card for a system design interview walkthrough on designing a CDN, showing a world map with edge points of presence near three clusters of users who are each served from the nearest edge on a cache hit, and only a rare cache miss travelling back to a single central origin, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the CDN round is actually run
&lt;/h2&gt;

&lt;p&gt;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 &lt;em&gt;how does a given user reach the right edge&lt;/em&gt; and &lt;em&gt;what happens on a cache miss&lt;/em&gt;, 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — the read:write ratio, the target cache-hit ratio, and request volume per PoP, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: the client, the routing layer, the edge PoPs, the regional/shield tier, and the origin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe it as a distributed read-through cache and name routing and freshness as the two hard problems), &lt;strong&gt;communication&lt;/strong&gt; (did you state the GeoDNS-versus-anycast trade-off and why versioned URLs beat a purge, each in a single sentence), &lt;strong&gt;technical execution&lt;/strong&gt; (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 &lt;strong&gt;time management&lt;/strong&gt; (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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what a CDN must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serve cached content (static assets and media) to users from a nearby location; fetch from the &lt;strong&gt;origin&lt;/strong&gt; on a &lt;strong&gt;cache miss&lt;/strong&gt; and cache it; honour per-object freshness (&lt;strong&gt;TTL&lt;/strong&gt; / &lt;code&gt;Cache-Control&lt;/code&gt;); support &lt;strong&gt;invalidation&lt;/strong&gt; — explicit &lt;strong&gt;purge&lt;/strong&gt; and &lt;strong&gt;versioned URLs&lt;/strong&gt;; route each client to the nearest healthy &lt;strong&gt;PoP&lt;/strong&gt;; terminate TLS at the edge.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;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.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Low latency&lt;/strong&gt; for users anywhere; very high &lt;strong&gt;read throughput&lt;/strong&gt;; a high &lt;strong&gt;cache-hit ratio&lt;/strong&gt; so most requests never leave the edge; &lt;strong&gt;protect and offload the origin&lt;/strong&gt; (it should barely feel global traffic); &lt;strong&gt;high availability&lt;/strong&gt; across PoPs (one PoP dying must not break the service); freshness is &lt;strong&gt;eventually consistent&lt;/strong&gt; — a changed object propagates quickly, not atomically.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"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."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving one thing: &lt;strong&gt;the cost is the miss, not the hit&lt;/strong&gt; — hits are cheap and local, and the whole design exists to keep the miss rate low and the miss cheap.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read:write ratio&lt;/td&gt;
&lt;td&gt;overwhelmingly reads — content is published rarely, read constantly&lt;/td&gt;
&lt;td&gt;optimise the &lt;strong&gt;read path&lt;/strong&gt; ruthlessly; writes (publishes/purges) are a slow control-plane concern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache-hit ratio&lt;/td&gt;
&lt;td&gt;target &lt;strong&gt;90%+&lt;/strong&gt; at the edge for cacheable content&lt;/td&gt;
&lt;td&gt;only &amp;lt;10% of requests become misses — so the origin is sized for the &lt;strong&gt;miss stream&lt;/strong&gt;, not total traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests per PoP&lt;/td&gt;
&lt;td&gt;high, bursty, concentrated on a small set of &lt;strong&gt;hot objects&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;a Zipf-like popularity curve — a few objects are most of the traffic, so &lt;strong&gt;replicate hot objects&lt;/strong&gt; and let the long tail be re-pulled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge storage&lt;/td&gt;
&lt;td&gt;finite per edge; far smaller than the full catalogue&lt;/td&gt;
&lt;td&gt;you &lt;strong&gt;cannot cache everything&lt;/strong&gt; — you need an eviction policy (&lt;strong&gt;LRU&lt;/strong&gt;) and the long tail lives at the origin&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, &lt;strong&gt;hits are free and misses are expensive&lt;/strong&gt;, 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 &lt;strong&gt;cache hierarchy&lt;/strong&gt;. Second, &lt;strong&gt;popularity is skewed&lt;/strong&gt;: 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: clients read objects, publishers write them to the origin, and the control plane invalidates.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /{object}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the hot path — served from the nearest edge; conditional reads use &lt;code&gt;If-None-Match: &amp;lt;etag&amp;gt;&lt;/code&gt; so a revalidation can return &lt;code&gt;304 Not Modified&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PUT /{object}&lt;/code&gt; (publish)&lt;/td&gt;
&lt;td&gt;the publisher writes a new object (or version) to the &lt;strong&gt;origin&lt;/strong&gt;; edges pick it up lazily on the next miss (pull) or via a push.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PURGE /{path}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;control-plane invalidation — drop an object (or prefix) from every edge immediately.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;versioned URL&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;…/app.&amp;lt;content-hash&amp;gt;.js&lt;/code&gt; — a new version is a &lt;strong&gt;new URL&lt;/strong&gt;, so no invalidation is needed at all.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model is thin. The &lt;strong&gt;origin is the source of truth&lt;/strong&gt;; the edge cache is derived and disposable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;object&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;key&lt;/code&gt;, &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;content_hash&lt;/code&gt;, &lt;code&gt;Cache-Control&lt;/code&gt;/&lt;code&gt;ttl&lt;/code&gt;, &lt;code&gt;ETag&lt;/code&gt;, &lt;code&gt;last_modified&lt;/code&gt; — the asset and its freshness metadata.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;edge_cache_entry&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(object_key, bytes, expires_at, last_access)&lt;/code&gt; — a cached copy at one edge; &lt;code&gt;last_access&lt;/code&gt; drives &lt;strong&gt;LRU&lt;/strong&gt; eviction; &lt;strong&gt;ephemeral&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;routing_map&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;PoP locations, health and load — used to pick the edge for a client.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;origin_mapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;which origin backs which hostname/path, plus the shield tier in front of it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: edges, the cache hierarchy and the origin
&lt;/h2&gt;

&lt;p&gt;Now the boxes. A request is answered as &lt;strong&gt;close to the user as possible&lt;/strong&gt;, and only the rare miss climbs the hierarchy toward a single origin:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing layer:&lt;/strong&gt; decides which &lt;strong&gt;PoP&lt;/strong&gt; serves a given client, using &lt;strong&gt;GeoDNS&lt;/strong&gt; and/or &lt;strong&gt;anycast&lt;/strong&gt;, and must be &lt;strong&gt;health- and load-aware&lt;/strong&gt; so a dead or overloaded PoP is taken out of rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge server (in a PoP):&lt;/strong&gt; holds cached objects and answers on a &lt;strong&gt;cache hit&lt;/strong&gt;. On a &lt;strong&gt;miss&lt;/strong&gt; it fetches from the next tier up, caches the object (respecting TTL), and serves it. Evicts with &lt;strong&gt;LRU&lt;/strong&gt; under finite storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regional edge cache / origin shield:&lt;/strong&gt; a mid-tier in front of the origin. Many edge misses for the same object collapse into &lt;strong&gt;one&lt;/strong&gt; fetch here, so the origin sees a tiny, smooth fraction of global traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin:&lt;/strong&gt; the &lt;strong&gt;source of truth&lt;/strong&gt; — your storage or application. Sized for the &lt;strong&gt;miss stream only&lt;/strong&gt;, protected by the shield.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control plane:&lt;/strong&gt; config, TTLs, and &lt;strong&gt;purge&lt;/strong&gt; commands; propagates invalidations out to the edges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback:&lt;/strong&gt; PoP &lt;strong&gt;health and load&lt;/strong&gt; signals flow back into the routing layer so routing follows reality, not a static map.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that this is a &lt;strong&gt;tiered read-through cache&lt;/strong&gt;: 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1xoc9hnso5c00rhf6rp.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1xoc9hnso5c00rhf6rp.jpeg" alt="A CDN architecture diagram showing a client sent by a routing layer to the nearest point of presence, where an edge server answers from cache on a hit, and on a miss climbs a hierarchy to a regional edge cache or origin shield and then to a single origin, with a control plane issuing purge and configuration and health and load signals feeding back into the routing layer." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: request routing — finding the nearest edge (GeoDNS vs anycast)
&lt;/h2&gt;

&lt;p&gt;This is the first decision the round is really about. The follow-up is the geography: &lt;em&gt;how does a user in Mumbai actually end up talking to a Mumbai edge, and what happens the moment that PoP dies?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are two well-known mechanisms, and the strong answer names both, picks a combination, and justifies it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;GeoDNS (DNS-based routing)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Anycast (BGP routing)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idea&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the CDN's authoritative &lt;strong&gt;DNS&lt;/strong&gt; resolves the hostname to the &lt;strong&gt;IP of a nearby edge&lt;/strong&gt;, chosen from the resolver's location, health and load&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;every PoP announces the same IP&lt;/strong&gt;; the internet's &lt;strong&gt;BGP&lt;/strong&gt; routing delivers the packet to the topologically &lt;strong&gt;nearest&lt;/strong&gt; PoP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load awareness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;high — DNS can steer away from a hot PoP and weight responses&lt;/td&gt;
&lt;td&gt;coarse — routing follows network topology, not your load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failover speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;as slow as the &lt;strong&gt;DNS TTL&lt;/strong&gt; (cached resolvers keep sending users to a dead edge until it expires)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;near-instant&lt;/strong&gt; — withdraw the route and traffic reroutes automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Precision&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;limited to the &lt;strong&gt;resolver&lt;/strong&gt;, not the user (public resolvers can mislocate)&lt;/td&gt;
&lt;td&gt;precise at the network level; but routes can &lt;strong&gt;flap&lt;/strong&gt; and move sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best for&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;load-aware steering, weighted rollouts&lt;/td&gt;
&lt;td&gt;fast failover, DDoS absorption, a single stable IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice you &lt;strong&gt;combine them&lt;/strong&gt;: 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 &lt;strong&gt;health-aware&lt;/strong&gt;, pulled from a live view of which PoPs are up and how loaded they are. Then say the one sentence that shows range: &lt;em&gt;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.&lt;/em&gt; That is the trade-off the round is testing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrlttahwan6chntztc06.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkrlttahwan6chntztc06.jpeg" alt="A diagram comparing GeoDNS and anycast for routing a user to the nearest CDN edge: on the GeoDNS side the CDN authoritative DNS resolves the hostname to the IP of a nearby healthy edge and failover waits for the DNS TTL to expire, while on the anycast side every point of presence announces the same IP and BGP delivers the packet to the topologically nearest point of presence so withdrawing a dead route reroutes traffic almost instantly." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Infrastructure · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design a CDN out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: cache population, freshness and invalidation
&lt;/h2&gt;

&lt;p&gt;Routing decides &lt;em&gt;where&lt;/em&gt; a request lands; this decides &lt;em&gt;what the edge holds&lt;/em&gt; and &lt;em&gt;whether you can trust it&lt;/em&gt;. The follow-up is the update: &lt;em&gt;you publish a new version of a file — how fast does every edge start serving it, and how do you make that happen?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start with how an edge even fills. There are two models, and you should name both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pull (origin-pull / lazy):&lt;/strong&gt; the edge caches an object on the &lt;strong&gt;first miss&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push:&lt;/strong&gt; the origin &lt;strong&gt;proactively pushes&lt;/strong&gt; 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then &lt;strong&gt;freshness&lt;/strong&gt; — how long an edge trusts what it holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TTL / &lt;code&gt;Cache-Control: max-age&lt;/code&gt;&lt;/strong&gt; sets how long an object is &lt;strong&gt;fresh&lt;/strong&gt;; while fresh, it is served with zero origin contact.&lt;/li&gt;
&lt;li&gt;On expiry, the edge &lt;strong&gt;revalidates&lt;/strong&gt; cheaply: &lt;code&gt;If-None-Match: &amp;lt;etag&amp;gt;&lt;/code&gt; (or &lt;code&gt;If-Modified-Since&lt;/code&gt;), and the origin replies &lt;code&gt;304 Not Modified&lt;/code&gt; if nothing changed — a tiny response instead of re-downloading the object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;stale-while-revalidate&lt;/strong&gt; serves the slightly stale copy &lt;strong&gt;instantly&lt;/strong&gt; and refreshes it in the background, so users never wait on a revalidation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally &lt;strong&gt;invalidation&lt;/strong&gt; — forcing a change before TTL expiry — where the strong candidate shows judgement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit purge:&lt;/strong&gt; a control-plane command drops an object (or prefix) from &lt;strong&gt;every edge&lt;/strong&gt;. It works, but it is a global operation with a brief race until all edges have cleared, and it is easy to over-purge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioned / fingerprinted URLs:&lt;/strong&gt; put a &lt;strong&gt;content hash&lt;/strong&gt; in the path (&lt;code&gt;app.9f2c1a.js&lt;/code&gt;). A new version is a &lt;strong&gt;new URL&lt;/strong&gt; 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 &lt;strong&gt;cleanest cache-busting&lt;/strong&gt; and the answer that scores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tie it together with the &lt;strong&gt;cache hierarchy&lt;/strong&gt;: because misses collapse at the &lt;strong&gt;origin shield&lt;/strong&gt;, even a cold object pulled by thousands of edges resolves to a handful of origin fetches, not thousands. Say it plainly: &lt;em&gt;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.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0mwfu23kyxqexyzcxdz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp0mwfu23kyxqexyzcxdz.jpeg" alt="A cache freshness and invalidation diagram showing an edge object carrying a Cache-Control max-age TTL that is served fresh until it expires and then revalidated against the origin with an ETag returning 304 Not Modified, alongside two ways to push a change: an explicit purge command from the control plane that drops a path from every edge, and a versioned fingerprinted URL where a new content hash makes a new URL that cannot collide with the cached old version." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Infrastructure · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend your freshness and invalidation model out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: scale, hot content and origin offload
&lt;/h2&gt;

&lt;p&gt;Routing and freshness keep a steady state healthy; this decides what happens under the two stresses that break a naive CDN — a &lt;strong&gt;viral object&lt;/strong&gt; and a &lt;strong&gt;dead origin&lt;/strong&gt;. The follow-up is the storm: &lt;em&gt;a file goes viral in one region and it isn't cached there yet — what stops a million simultaneous misses from flattening your origin?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is a stack of defences, each worth naming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object placement with consistent hashing:&lt;/strong&gt; within a PoP, objects are spread across the edge servers by &lt;strong&gt;consistent hashing&lt;/strong&gt;, 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replicate hot objects:&lt;/strong&gt; a Zipf-hot object is deliberately &lt;strong&gt;replicated to more servers&lt;/strong&gt; so a single viral file doesn't melt one box; popularity, not a uniform hash, decides how many copies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request coalescing (the key defence):&lt;/strong&gt; when many clients miss on the &lt;strong&gt;same&lt;/strong&gt; object at once, the edge issues &lt;strong&gt;one&lt;/strong&gt; origin fetch and makes everyone else &lt;strong&gt;wait on that single in-flight request&lt;/strong&gt;. A million concurrent misses become one upstream fetch — this is what actually kills the &lt;strong&gt;thundering herd&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Origin shield:&lt;/strong&gt; coalescing repeats at the &lt;strong&gt;shield tier&lt;/strong&gt;, so even misses from many PoPs for the same object collapse to one origin fetch. The origin sees a trickle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serve stale on failure:&lt;/strong&gt; if the origin is &lt;strong&gt;slow or down&lt;/strong&gt;, edges &lt;strong&gt;serve the stale copy&lt;/strong&gt; past its TTL rather than returning an error — availability beats strict freshness for cached content. The shield keeps retrying in the background.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say the point plainly: &lt;strong&gt;the origin must never feel the crowd.&lt;/strong&gt; 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The origin goes down — what do the edges serve?" They keep serving: on a miss or an expiry they serve the &lt;strong&gt;stale&lt;/strong&gt; 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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the CDN round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"How does a user reach the nearest edge, and what happens when a PoP dies?"&lt;/strong&gt; → 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)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Pull or push — how do edges get their content?"&lt;/strong&gt; → 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)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"You change a file — how do you invalidate it everywhere?"&lt;/strong&gt; → 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)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A file goes viral and isn't cached — what stops the origin from melting?"&lt;/strong&gt; → 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)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The origin is down — what do users see?"&lt;/strong&gt; → 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)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the CDN is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design a CDN step by step?
&lt;/h3&gt;

&lt;p&gt;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/&lt;code&gt;Cache-Control&lt;/code&gt;, 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 (&lt;code&gt;GET&lt;/code&gt; with conditional revalidation, publish to origin, &lt;code&gt;PURGE&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a CDN route a user to the nearest edge?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the difference between a pull and a push CDN?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does cache invalidation work in a CDN?
&lt;/h3&gt;

&lt;p&gt;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 (&lt;code&gt;ETag&lt;/code&gt;/&lt;code&gt;If-None-Match&lt;/code&gt; returning &lt;code&gt;304 Not Modified&lt;/code&gt;), 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does a CDN handle a viral spike without overloading the origin?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read what a cache hit is. The round is won on protecting the origin out loud.&lt;/h3&gt;

&lt;p&gt;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. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the CDN round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>interview</category>
      <category>dataengineering</category>
      <category>walkin</category>
    </item>
    <item>
      <title>Design an ad click aggregator: the system design interview walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Sun, 18 Oct 2026 21:46:48 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-an-ad-click-aggregator-the-system-design-interview-walkthrough-3cl0</link>
      <guid>https://dev.to/gowthampotureddi/design-an-ad-click-aggregator-the-system-design-interview-walkthrough-3cl0</guid>
      <description>&lt;h2&gt;
  
  
  Design an ad click aggregator: the system design interview walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design an ad click aggregator&lt;/strong&gt; sounds like one line — count the clicks on each ad — and that one line hides the whole interview. Picture it for one advertiser and a &lt;code&gt;COUNT(*)&lt;/code&gt; settles it; picture it at ad-network scale, where tens of thousands of clicks land every second and an advertiser wants clicks-per-minute for their campaign updated almost live, and you quickly see you can neither scan a billion raw rows per query nor keep a perfect running total in one place. This is one of the most revealing &lt;strong&gt;real-time analytics&lt;/strong&gt; prompts — the Google-Ads-style question — because it is the textbook &lt;strong&gt;scaling-writes&lt;/strong&gt; problem: the firehose of writes dwarfs the trickle of reads, so the entire design bends around ingesting a torrent without losing a click and turning it into numbers you can actually query.&lt;/p&gt;

&lt;p&gt;The round turns on one structural insight. "Count the clicks" is not a counter — it is a &lt;strong&gt;streaming aggregation pipeline&lt;/strong&gt;. You cannot afford to store every raw click and re-scan it per query, so you &lt;strong&gt;buffer the firehose in a log&lt;/strong&gt;, &lt;strong&gt;pre-aggregate it in a stream processor over time windows&lt;/strong&gt;, and serve cheap reads from &lt;strong&gt;pre-computed rollups&lt;/strong&gt; (clicks per ad per minute) in an analytics store. And there is a second twist that separates a good answer from a great one: those counts are &lt;strong&gt;billing numbers&lt;/strong&gt;, so "fast but roughly right" is not enough — you add a slower, authoritative path that re-computes the counts from the raw events and reconciles them, which is exactly the &lt;strong&gt;lambda architecture&lt;/strong&gt; (a fast speed layer plus a correct batch layer). Get that reframe — ingest to a log, aggregate the stream, serve rollups, reconcile for accuracy — and the rest of the &lt;strong&gt;system design interview&lt;/strong&gt; is deciding how not to lose a click, how to bucket clicks by the minute they actually happened, and how to keep the totals exact enough to charge money against. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1uelqv03wcrvyrg043m8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1uelqv03wcrvyrg043m8.jpeg" alt="Title card for a system design interview walkthrough on designing an ad click aggregator, showing a firehose of click events flowing into a log, a stream processor aggregating them into per-minute rollups for advertiser dashboards, and a parallel batch layer reconciling the counts for billing, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the ad click aggregator round is actually run
&lt;/h2&gt;

&lt;p&gt;An ad click aggregator 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 "count clicks," the question becomes &lt;em&gt;what happens to a single click at scale&lt;/em&gt; and &lt;em&gt;how is a per-minute number ready to query&lt;/em&gt;, 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 ingestion, aggregation and accuracy, not on the ad-serving side.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "count the clicks" stand. "You're taking in tens of thousands of clicks a second, and an advertiser wants clicks-per-minute for their ad on a dashboard — you're obviously not going to run &lt;code&gt;COUNT(*)&lt;/code&gt; over a billion rows per query, so what actually happens to a single click from the moment it lands?" That question — the life of a click in a high-write pipeline — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — clicks per second versus advertiser query rate, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: ingest to a log, aggregate in a stream processor, serve rollups, and a batch path to reconcile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you reframe it as scaling writes — buffer, pre-aggregate, serve rollups — and add a batch layer for accuracy), &lt;strong&gt;communication&lt;/strong&gt; (did you say why a log buffers the firehose and why you bucket by event time, in a single sentence), &lt;strong&gt;technical execution&lt;/strong&gt; (a partitioned, retained log; a stream processor doing event-time windowed aggregation with watermarks and exactly-once; idempotent writes and a reconciling batch job), and &lt;strong&gt;time management&lt;/strong&gt; (did you spend your 45 minutes on ingestion, aggregation and accuracy, not on ad targeting). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what an ad click aggregator must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Record every ad click; let advertisers query aggregated metrics — clicks per ad over time, e.g. at per-minute granularity; power near-real-time dashboards; the counts are accurate enough to bill on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Extra dimensions (geo, device, campaign), click-through-rate, fraud / invalid-click filtering, longer rollup tiers (hourly, daily) — call these out, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very high &lt;strong&gt;write throughput&lt;/strong&gt; with &lt;strong&gt;no data loss&lt;/strong&gt;; low-latency reads over aggregates; &lt;strong&gt;accuracy&lt;/strong&gt; (eventually exact, because it drives billing); horizontally scalable; resilient to spikes and processor failures.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"This is a scaling-writes problem — clicks hugely outnumber queries — so I buffer the firehose in a log, pre-aggregate it in a stream processor into per-minute rollups I can query cheaply, and because these counts are billed, I add a batch layer to re-compute from raw events and reconcile."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving one thing: &lt;strong&gt;writes overwhelmingly dominate reads&lt;/strong&gt;, so the system is built around ingesting and pre-aggregating, not around querying raw data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clicks&lt;/td&gt;
&lt;td&gt;tens of thousands per second at peak, bursty&lt;/td&gt;
&lt;td&gt;a &lt;strong&gt;firehose&lt;/strong&gt; of writes — the scaling axis; it must be buffered and never dropped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advertiser queries&lt;/td&gt;
&lt;td&gt;a comparatively tiny number of dashboard reads&lt;/td&gt;
&lt;td&gt;reads are trivial &lt;strong&gt;if&lt;/strong&gt; they hit pre-aggregated rollups, not raw events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw event volume&lt;/td&gt;
&lt;td&gt;billions of click rows accumulating&lt;/td&gt;
&lt;td&gt;too large to scan per query — archive raw to a &lt;strong&gt;data lake&lt;/strong&gt;, serve from rollups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aggregation granularity&lt;/td&gt;
&lt;td&gt;per-minute buckets per ad&lt;/td&gt;
&lt;td&gt;a &lt;strong&gt;bounded&lt;/strong&gt; number of small rollup rows — cheap to store and query&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, &lt;strong&gt;writes are the whole problem&lt;/strong&gt; — the click rate dwarfs the query rate, so the architecture (a log to absorb the firehose, a stream processor to pre-aggregate, partitioning by ad id) exists to handle write throughput without losing data. Second, &lt;strong&gt;reads are easy once you pre-aggregate&lt;/strong&gt; — an advertiser querying clicks-per-minute reads a handful of small rollup rows, never a billion raw clicks, so the read path is almost an afterthought &lt;em&gt;if&lt;/em&gt; the write path did its job. You have now pointed the interview at the real deep dives — ingestion, aggregation, and accuracy — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: clicks go in, aggregated metrics come out.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;record_click(ad_id, user_id, ts, metadata)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;fired when an ad is clicked; appended to the log as a raw event — fast and fire-and-forget.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_metrics(ad_id, time_range, granularity)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns aggregated clicks over the range (e.g. per minute) from the rollup store.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model is thin, and split by purpose: raw events are the durable truth; rollups are the query surface.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;click_event&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(ad_id, user_id, event_ts, metadata)&lt;/code&gt; — the raw click, held in the &lt;strong&gt;log&lt;/strong&gt; (short-term) and archived to a &lt;strong&gt;data lake&lt;/strong&gt; (long-term) for reprocessing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aggregate_rollup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;(ad_id, window_start, count)&lt;/code&gt; — pre-computed per-ad, per-window counts in an &lt;strong&gt;OLAP / time-series store&lt;/strong&gt;; this is what advertiser queries read.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: ingest, aggregate, query (and reconcile)
&lt;/h2&gt;

&lt;p&gt;Now the boxes. A click's whole life is a pipeline from a lightweight ingest service, through a log, into a stream processor, out to rollups — with a second, slower path for correctness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Click-processing service:&lt;/strong&gt; receives &lt;code&gt;record_click&lt;/code&gt; and does almost nothing but &lt;strong&gt;append the event to a log&lt;/strong&gt; — stateless and fast, so it can absorb the firehose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log (Kafka / Kinesis):&lt;/strong&gt; the backbone. It &lt;strong&gt;buffers&lt;/strong&gt; spikes, &lt;strong&gt;decouples&lt;/strong&gt; ingest from the slower aggregator, is &lt;strong&gt;partitioned by ad id&lt;/strong&gt;, and is &lt;strong&gt;replicated and retained&lt;/strong&gt; (e.g. 7 days) so nothing is lost and a crashed consumer can &lt;strong&gt;replay&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream processor (Flink / Spark Streaming):&lt;/strong&gt; consumes the log and &lt;strong&gt;aggregates clicks per ad per time window&lt;/strong&gt;, then &lt;strong&gt;flushes rollups&lt;/strong&gt; to the analytics store. This is the speed layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OLAP / time-series store:&lt;/strong&gt; holds the small per-ad, per-minute rollups; a &lt;strong&gt;query service&lt;/strong&gt; serves advertiser dashboards cheap reads over them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data lake + batch job (the batch layer):&lt;/strong&gt; raw events are also archived to a &lt;strong&gt;data lake&lt;/strong&gt; (S3), where a &lt;strong&gt;periodic batch job&lt;/strong&gt; re-aggregates them from scratch and &lt;strong&gt;reconciles&lt;/strong&gt; the rollups — the authoritative, billing-grade numbers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that this is a &lt;strong&gt;write-side aggregation pipeline with two layers&lt;/strong&gt;: a fast stream path for low-latency approximate counts, and a slower batch path for exact, billable counts, both feeding the same rollups. Naming that — log, stream processor, rollups, and a reconciling batch job — is a signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fluwib1n6qn6i8kizbt18.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fluwib1n6qn6i8kizbt18.jpeg" alt="An ad click aggregator architecture diagram showing a browser click hitting a click-processing service that appends the event to a distributed log partitioned by ad id, a stream processor consuming the log and aggregating clicks per ad per time window before flushing rollups to an OLAP time-series store that a query service serves to advertiser dashboards, and a parallel path where raw events are archived to a data lake and a periodic batch job re-aggregates and reconciles the rollups." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: ingesting the click firehose without losing data
&lt;/h2&gt;

&lt;p&gt;This is the first decision that actually decides the round. The follow-up is about the spike: &lt;em&gt;a viral moment triples the click rate and your aggregator falls behind — do you drop clicks?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is no, and the reason you do not have to is the &lt;strong&gt;log&lt;/strong&gt;. The click-processing service does the minimum — append each click to a distributed log like &lt;strong&gt;Kafka or Kinesis&lt;/strong&gt; — and the log does the heavy lifting that makes the firehose survivable:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Log property&lt;/th&gt;
&lt;th&gt;What it buys&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Buffering&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the log absorbs bursts; if the aggregator falls behind, clicks queue durably instead of being dropped — the processor catches up when the spike passes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decoupling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ingest and aggregation scale independently; a slow consumer never backs pressure onto the click-processing service.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partitioning by ad id&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;clicks for an ad land on the same partition, so aggregation parallelises across partitions and ordering per ad is preserved.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Replication + retention&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the log is replicated across brokers/zones (no loss if a node dies) and &lt;strong&gt;retained&lt;/strong&gt; for days, so a crashed consumer &lt;strong&gt;replays&lt;/strong&gt; from its last offset.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The reason a spike cannot force you to drop clicks is that the log is &lt;strong&gt;durable storage, not a transient pipe&lt;/strong&gt; — it holds the events until the processor has consumed them, and keeps holding them for the retention window even after. If the stream processor crashes, it comes back and resumes from its committed offset, re-reading anything it missed. Saying plainly that the log &lt;em&gt;is&lt;/em&gt; the buffer and the safety net — not just a message bus — is the sentence that separates a real answer from a hand-wave.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A traffic spike triples the click rate and your aggregator falls behind — do you drop clicks?" No — the click-processing service only appends each click to a durable, replicated log like Kafka, which buffers the spike. The aggregator consumes at its own pace and simply catches up once the burst passes; because the log is retained for days, even a crashed processor replays from its last offset and loses nothing. The log is the buffer and the safety net, so backpressure never reaches ingestion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Media &amp;amp; analytics · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design an ad click aggregator out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: aggregating the stream over time windows
&lt;/h2&gt;

&lt;p&gt;Ingestion keeps every click; this turns the stream into numbers. The follow-up is the one that trips people: &lt;em&gt;a click that happened at 11:59 doesn't arrive until 12:01 — which minute does it count in, and how do you even know the 11:59 window is finished?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The job is to aggregate clicks &lt;strong&gt;per ad, per time window&lt;/strong&gt; — tumbling one-minute buckets — and the subtlety is &lt;em&gt;which&lt;/em&gt; time you bucket by. A &lt;strong&gt;stream processor&lt;/strong&gt; like &lt;strong&gt;Flink&lt;/strong&gt; (or Spark Streaming) consumes the log and does this properly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event time, not arrival time:&lt;/strong&gt; bucket each click by &lt;strong&gt;when the click actually happened&lt;/strong&gt; (&lt;code&gt;event_ts&lt;/code&gt;), not when the processor received it. A click from 11:59 that arrives at 12:01 still counts in the &lt;strong&gt;11:59&lt;/strong&gt; window — otherwise network lag would smear your numbers across the wrong minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watermarks close windows:&lt;/strong&gt; a &lt;strong&gt;watermark&lt;/strong&gt; tracks how far event time has progressed, so the processor knows when it is safe to say "the 11:59 window is done" and &lt;strong&gt;flush&lt;/strong&gt; it — while still tolerating a bounded amount of &lt;strong&gt;late, out-of-order&lt;/strong&gt; arrival.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exactly-once, fault-tolerant state:&lt;/strong&gt; the processor keeps running per-window counts as managed state with &lt;strong&gt;exactly-once&lt;/strong&gt; guarantees and recovers that state after a failure — which is why you reach for a real stream processor rather than a hand-rolled consumer keeping a count in memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flush rollups to the store:&lt;/strong&gt; when a window closes, its per-ad counts are written to the &lt;strong&gt;OLAP / time-series store&lt;/strong&gt; as rollups. You can shrink the window or flush more often to trade &lt;strong&gt;latency against completeness&lt;/strong&gt; — tighter windows mean fresher dashboards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot ads:&lt;/strong&gt; a viral ad can hotspot one partition; split the hot key into sub-keys (&lt;code&gt;adA#0 … adA#n&lt;/code&gt;) for a first aggregation pass and merge in a second — &lt;strong&gt;two-stage aggregation&lt;/strong&gt; — so no single task is a bottleneck.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: &lt;strong&gt;event time plus watermarks&lt;/strong&gt; is what makes the counts correct under the messiness of a real network, where clicks arrive late and out of order. A naive per-minute consumer that counts by arrival time is easy to describe and quietly wrong; saying &lt;em&gt;why&lt;/em&gt; you bucket by event time is the signal of someone who has thought about streaming, not just drawn it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A click from 11:59 arrives at 12:01 — which minute does it count in, and how do you know the 11:59 window is done?" It counts in 11:59, because the processor buckets by the click's event time, not its arrival time. A watermark tracks how far event time has advanced, so once it passes 11:59 (allowing a small grace for late arrivals) the processor knows that window is complete, flushes its per-ad counts to the rollup store, and still slots a slightly-late click into the right bucket. Event time plus watermarks is what keeps the minute-by-minute numbers honest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F90gnozx3c5iui9d617uc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F90gnozx3c5iui9d617uc.jpeg" alt="A windowed stream aggregation diagram showing click events each tagged with the event time they occurred, sorted into per-minute tumbling window buckets by event time rather than arrival time, a late click from the 11:59 minute arriving at 12:01 still landing in the 11:59 bucket, and a watermark marking how far event time has progressed so the processor knows when a window is complete and can flush its per-ad count to the rollup store." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Media &amp;amp; analytics · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend event-time windowed aggregation out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: keeping the counts accurate — idempotency and reconciliation
&lt;/h2&gt;

&lt;p&gt;Windowing gives you fast numbers; this makes them numbers you can put on an invoice. The follow-up is the one with money attached: &lt;em&gt;your stream double-counted a window after a crash, and this number is a bill — how do you make it right?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two different accuracy problems need two different fixes, and naming both is what separates a senior answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency / exactly-once writes (stop double-counting):&lt;/strong&gt; the classic failure is the processor flushing a window's rollup to the store but crashing &lt;strong&gt;before&lt;/strong&gt; committing its stream offset — on restart it reprocesses the same window and &lt;strong&gt;over-counts&lt;/strong&gt;. Fix it by making the flush &lt;strong&gt;idempotent&lt;/strong&gt;: commit the aggregated result &lt;strong&gt;and&lt;/strong&gt; the stream offset &lt;strong&gt;atomically&lt;/strong&gt; (one transaction, or an upsert keyed by &lt;code&gt;(ad_id, window)&lt;/code&gt; so re-applying is a no-op). Now a replay after a crash cannot inflate the count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Batch reconciliation (correct the drift):&lt;/strong&gt; even with idempotent writes, transient processing errors, a bad deploy, or extreme out-of-order events leave the speed layer &lt;strong&gt;slightly&lt;/strong&gt; off. So also archive the &lt;strong&gt;raw events to a data lake&lt;/strong&gt; (S3) and run a &lt;strong&gt;periodic batch job&lt;/strong&gt; (e.g. a daily Spark job) that &lt;strong&gt;re-aggregates from the raw events&lt;/strong&gt; and &lt;strong&gt;reconciles&lt;/strong&gt; — correcting the real-time rollups to the exact counts. The batch result is the &lt;strong&gt;billing-grade source of truth&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pairing — a fast &lt;strong&gt;speed layer&lt;/strong&gt; (the stream) for low-latency dashboards plus a slower &lt;strong&gt;batch layer&lt;/strong&gt; (re-aggregation from raw) for correctness — is the &lt;strong&gt;lambda architecture&lt;/strong&gt;. The dashboard shows near-real-time approximate counts within seconds; the batch job guarantees that what you actually bill is exact. While you are here, name &lt;strong&gt;fraud / invalid-click filtering&lt;/strong&gt; (bots, repeated clicks from one user) as a real concern, even if you scope the detail out — billable clicks have to be &lt;em&gt;valid&lt;/em&gt; clicks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your stream double-counted a window after a crash and this number is a bill — how do you make it right?" Two ways working together. First, I make the flush idempotent — the window's count and the stream offset commit atomically, so a replay after a crash can't over-count. Second, I keep the raw events in a data lake and run a periodic batch job that re-aggregates them and reconciles the rollups, so the billed number is recomputed from source and exact. Fast approximate counts from the speed layer, authoritative counts from the batch layer — the lambda architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmle0asuj77yds2lfzgcn.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmle0asuj77yds2lfzgcn.jpeg" alt="A Lambda architecture and idempotency diagram showing a fast speed layer where a stream processor writes per-window rollups and commits its stream offset atomically so a crash and replay cannot double-count, alongside a batch layer where raw events archived in a data lake are periodically re-aggregated by a batch job and reconciled against the real-time rollups to produce the billing-grade source of truth." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the ad click aggregator round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"A spike triples the click rate — do you drop clicks?"&lt;/strong&gt; → No: the click-processing service appends to a durable, replicated, retained log that buffers the burst; the aggregator catches up at its own pace and a crashed processor replays from its offset, so nothing is lost. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How do you serve clicks-per-minute without scanning raw events?"&lt;/strong&gt; → You pre-aggregate on the write side: a stream processor rolls clicks up per ad per minute into an OLAP store, and advertiser queries read small rollup rows, never the billions of raw clicks. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"A click arrives two minutes late — which window does it count in?"&lt;/strong&gt; → The one matching its event time, not arrival time; a watermark tells the processor when a window is safe to close while still admitting bounded late events, so the minute-by-minute counts stay correct. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The stream double-counted after a crash — how do you fix a billed number?"&lt;/strong&gt; → Make the flush idempotent (commit count and offset atomically so replays don't over-count), and reconcile with a periodic batch re-aggregation from raw events in a data lake — the authoritative total. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Why keep both a stream and a batch path?"&lt;/strong&gt; → The lambda architecture: the speed layer gives near-real-time approximate dashboards, the batch layer recomputes exact, billing-grade counts from raw events — fast and correct, instead of choosing one. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do the ad click aggregator end to end on paper — requirements, estimate, API, data model, and the ingest/aggregate/query/reconcile boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill ingestion and aggregation: why a log buffers the firehose and enables replay, and how a stream processor windows clicks by event time with watermarks. Say each tradeoff in one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own accuracy — idempotent flushes to stop double-counting, and the batch reconciliation that makes counts billable (the lambda architecture). Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once the ad click aggregator is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design an ad click aggregator step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (record every click; let advertisers query aggregated clicks over time, e.g. per minute; near-real-time dashboards; counts accurate enough to bill); estimate to show that writes (tens of thousands of clicks per second) massively dominate reads, so it is a scaling-writes problem; define the small API (record_click appended to a log, get_metrics reading rollups) with a raw-event-vs-rollup data model; draw the design (click-processing service → a partitioned, retained log like Kafka → a stream processor aggregating per ad per window → an OLAP/time-series store of rollups → a query service, plus raw events to a data lake and a periodic batch job to reconcile); then deep-dive ingestion without data loss, windowed aggregation, and accuracy. Finish on the follow-ups about spikes and double-counting.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you ingest a high-volume click stream without losing data?
&lt;/h3&gt;

&lt;p&gt;By putting a durable distributed log between ingestion and aggregation. The click-processing service does almost nothing but append each click to a log like Kafka or Kinesis, which buffers spikes, decouples the fast ingest path from the slower aggregator, partitions clicks by ad id for parallelism, and replicates the data across brokers or availability zones so a node failure loses nothing. With a retention window of several days, even a crashed stream processor can replay from its last committed offset and re-read anything it missed. Because the log is durable storage rather than a transient pipe, a traffic spike simply queues up and the aggregator catches up afterward — backpressure never forces you to drop clicks.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does stream processing aggregate clicks over time windows?
&lt;/h3&gt;

&lt;p&gt;A stream processor such as Flink or Spark Streaming consumes the log and maintains running counts per ad per time window — typically tumbling one-minute buckets — then flushes those rollups to an analytics store. The key subtlety is that it buckets clicks by event time (when the click happened) rather than processing time (when it arrived), so a click delayed by the network still lands in the correct minute. Watermarks track how far event time has advanced so the processor knows when a window is complete and safe to flush while still admitting bounded late events, and its windowed state is kept with exactly-once guarantees and recovered after failures. That is why a purpose-built stream processor is preferred over a hand-rolled consumer counting in memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the lambda architecture and why reconcile the counts?
&lt;/h3&gt;

&lt;p&gt;The lambda architecture pairs a fast speed layer with a correct batch layer. The speed layer is the stream processor, which produces near-real-time but approximate counts for dashboards. The batch layer archives the raw click events to a data lake and periodically — say daily with a Spark job — re-aggregates them from scratch and reconciles the rollups, producing exact, authoritative numbers. You reconcile because transient processing errors, bad deploys, and extreme out-of-order events can leave the real-time counts slightly off, and in an ad system those counts are billed. Combining both layers lets the system be fast for monitoring and exact for billing, instead of forcing a choice between latency and correctness.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you keep ad click counts accurate enough to bill on?
&lt;/h3&gt;

&lt;p&gt;With idempotency on the write side and reconciliation on the batch side. Idempotency prevents double-counting: if the stream processor flushes a window but crashes before committing its offset, a naive restart would reprocess and over-count, so you commit the aggregated result and the stream offset atomically (or upsert keyed by ad and window) to make reprocessing a no-op. Reconciliation corrects residual drift: a periodic batch job re-aggregates from the raw events stored in a data lake and overwrites the rollups with exact totals, which become the billing source of truth. You also filter fraudulent and invalid clicks, since only valid clicks should be billed. Together these make the numbers trustworthy enough to charge against.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read why the log buffers the firehose. The round is won on saying it under follow-ups.&lt;/h3&gt;

&lt;p&gt;Pick the Ad Click Aggregator round, draw the ingest-aggregate-query design on the canvas, and defend it out loud to a voice interviewer that keeps asking "a spike triples the clicks — do you drop any?" and "this count is a bill — how do you make it exact?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the Ad Click Aggregator round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>interview</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
    <item>
      <title>Design Pastebin: the beginner-friendly system design interview walkthrough</title>
      <dc:creator>Gowtham Potureddi</dc:creator>
      <pubDate>Sun, 18 Oct 2026 21:43:43 +0000</pubDate>
      <link>https://dev.to/gowthampotureddi/design-pastebin-the-beginner-friendly-system-design-interview-walkthrough-3em4</link>
      <guid>https://dev.to/gowthampotureddi/design-pastebin-the-beginner-friendly-system-design-interview-walkthrough-3em4</guid>
      <description>&lt;h2&gt;
  
  
  Design Pastebin: the beginner-friendly system design interview walkthrough
&lt;/h2&gt;

&lt;p&gt;The request to &lt;strong&gt;design Pastebin&lt;/strong&gt; sounds like one line — paste some text, get a short link, share it — and that one line is exactly why it is the best first &lt;strong&gt;system design interview&lt;/strong&gt; you can practise. It looks almost identical to a URL shortener, so the moves are foundational, yet it hides one real twist that teaches the core of storage design: a shortener remembers a tiny web address, but &lt;strong&gt;Pastebin stores and serves arbitrary-size content that has to expire&lt;/strong&gt;. That single difference is what turns a toy into a proper HLD, and learning to spot it is the whole lesson.&lt;/p&gt;

&lt;p&gt;The round turns on one structural insight. "Paste text, get a link" looks like a shortener until you ask where a megabyte of pasted logs actually goes — and the answer is &lt;em&gt;not&lt;/em&gt; in the little database that maps your link to it. The body of a paste belongs in &lt;strong&gt;blob (object) storage&lt;/strong&gt;, kept apart from the small, queryable &lt;strong&gt;metadata&lt;/strong&gt; that records the key, where the body lives, and when it expires. From there everything follows: the system is overwhelmingly &lt;strong&gt;read-heavy&lt;/strong&gt; (a paste is written once and read many times), so it is &lt;strong&gt;cache-first&lt;/strong&gt; behind a CDN; and because every paste carries a &lt;strong&gt;TTL&lt;/strong&gt;, old content is swept away and the storage footprint stays small. Get that reframe — a URL shortener plus arbitrary-size content plus expiry — and the rest of the &lt;strong&gt;Pastebin system design&lt;/strong&gt; is three clean decisions: how to mint a short unique key, where the body lives and how reads scale, and how pastes expire. 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9h4hn63cbnxn6cjfrda.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm9h4hn63cbnxn6cjfrda.jpeg" alt="Title card for a beginner-friendly system design interview walkthrough on designing Pastebin, showing text being pasted and turned into a short link, the large body stored in a blob store separate from a small metadata database, served read-heavy through a cache, with a TTL clock marking expiry, on a dark citrus-and-lavender background." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Pastebin round is actually run
&lt;/h2&gt;

&lt;p&gt;A Pastebin 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 &lt;em&gt;first&lt;/em&gt; system because the requirements are small enough to hold in your head, so the round is really a test of judgement: can you keep a simple product simple while still making the right calls on storage, read scaling and cleanup, instead of either under-thinking it ("just a database") or over-building it with machinery it does not need. The clock is a touch tighter than the hour-long rounds, which suits a focused problem — spend your minutes on the key, the storage split, and expiry.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The interviewer rarely lets "it's basically a URL shortener" stand. "Fine — so what's actually different from a shortener, and when someone pastes a megabyte of logs, where does that text go and why not just put it in the database?" That question — content storage, not link mapping — is the one a copied answer can never rehearse, and the one this round is really testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The rounds here are &lt;strong&gt;modelled on how the loop runs&lt;/strong&gt;, 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  The five-step framework for any HLD round
&lt;/h3&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clarify requirements&lt;/strong&gt; — functional and non-functional, and what you are explicitly &lt;em&gt;not&lt;/em&gt; building.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimate&lt;/strong&gt; — reads versus writes and the size of a paste, to justify every later choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-level design&lt;/strong&gt; — the boxes and arrows: the write path that mints a key and stores the body, and the read-heavy path that serves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deep dive&lt;/strong&gt; — the two or three decisions that actually decide this system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow-ups&lt;/strong&gt; — bottlenecks, failure modes, and what you would change at ten times the scale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What the debrief measures
&lt;/h3&gt;

&lt;p&gt;The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: &lt;strong&gt;problem solving&lt;/strong&gt; (did you see past "URL shortener" to arbitrary-size, read-heavy, expiring content), &lt;strong&gt;communication&lt;/strong&gt; (did you say why the body goes to blob storage and not the metadata DB, in a single sentence), &lt;strong&gt;technical execution&lt;/strong&gt; (a collision-free key, metadata separate from the blob, cache-first reads behind a CDN, and a TTL with lazy delete plus cleanup), and &lt;strong&gt;time management&lt;/strong&gt; (did you keep a simple product simple and spend your 45 minutes on storage and expiry, not on re-deriving base62). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Requirements: what Pastebin must do
&lt;/h2&gt;

&lt;p&gt;Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A user pastes text and gets back a short, unique link; anyone opening that link reads the content; the user can set an expiry; pastes can be public, unlisted, or private.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Functional (optional)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Syntax highlighting, custom aliases, edit-after-create, view analytics — call these out, then scope most of them for later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Non-functional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Read-heavy&lt;/strong&gt; (far more reads than writes); highly available; content durable until it expires; low-latency reads; cheap storage that does not grow without bound.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The sentence that earns points: &lt;em&gt;"It's a URL shortener plus two things — the body is arbitrary-size content, so it lives in blob storage separate from the metadata, and every paste expires, so the footprint stays small; and it's read-heavy, so it's cache-first."&lt;/em&gt; Say that and you have framed the entire design before drawing a box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Capacity estimation (back of the envelope)
&lt;/h2&gt;

&lt;p&gt;Estimation here is about proving three things: reads dominate writes, a paste can be large, and expiry keeps storage bounded.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Quantity&lt;/th&gt;
&lt;th&gt;Assumption&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Writes&lt;/td&gt;
&lt;td&gt;modest — some pastes per second at peak&lt;/td&gt;
&lt;td&gt;ordinary write volume; the write path is not the scaling problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reads&lt;/td&gt;
&lt;td&gt;each paste read many times (shared links)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;reads dominate by orders of magnitude&lt;/strong&gt; — the design must be cache-first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paste size&lt;/td&gt;
&lt;td&gt;a few lines up to several megabytes&lt;/td&gt;
&lt;td&gt;too big to sit inline in the metadata DB — the body belongs in &lt;strong&gt;blob / object storage&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active footprint&lt;/td&gt;
&lt;td&gt;daily writes × an expiry window of days&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;bounded&lt;/strong&gt; — expiry caps total storage at roughly a few days of pastes, not forever&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two conclusions fall out, and you should say both out loud. First, &lt;strong&gt;reads are the axis that matters&lt;/strong&gt; — writes are cheap and occasional, but the same link can be opened thousands of times, so the design leans on a &lt;strong&gt;cache and a CDN&lt;/strong&gt; and treats storage as read-mostly. Second, &lt;strong&gt;expiry is what keeps it cheap&lt;/strong&gt; — because every paste has a TTL, you are never storing the whole history of the internet, just a rolling window, which also shapes how you clean up. You have now pointed the interview at the real deep dives — the key, the storage split, and expiry — before anyone asked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — API and data model
&lt;/h2&gt;

&lt;p&gt;The API is deliberately small: create a paste, read it, maybe delete it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create_paste(content, expiry, visibility)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;stores the body, writes metadata, and returns the short &lt;code&gt;key&lt;/code&gt; / URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;get_paste(key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns the paste content (or a 404 if missing or expired).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;delete_paste(key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;removes a paste before its expiry (owner action).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The data model is thin, and the whole trick is in the split: small metadata in a datastore, the large body in a blob store.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;paste_metadata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;key&lt;/code&gt;, &lt;code&gt;blob_location&lt;/code&gt;, &lt;code&gt;created_at&lt;/code&gt;, &lt;code&gt;expires_at&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, &lt;code&gt;visibility&lt;/code&gt; — small, queryable, keyed by the short key; the index into everything.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;paste_body&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the raw pasted text, stored in &lt;strong&gt;blob / object storage&lt;/strong&gt; at &lt;code&gt;blob_location&lt;/code&gt; — large and opaque, never inline in the metadata DB.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Step 4 — High-level design: the write path and the read path
&lt;/h2&gt;

&lt;p&gt;Now the boxes. Clients hit &lt;strong&gt;stateless app servers&lt;/strong&gt; behind a &lt;strong&gt;load balancer&lt;/strong&gt;, and the system splits cleanly into a modest write path and a read-heavy read path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write path:&lt;/strong&gt; an app server asks a &lt;strong&gt;key-generation service&lt;/strong&gt; for a short unique key, stores the body in the &lt;strong&gt;blob / object store&lt;/strong&gt;, writes the &lt;strong&gt;metadata&lt;/strong&gt; (&lt;code&gt;key → blob location, created, expires_at, size, visibility&lt;/code&gt;) to the datastore, and returns the key as a URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read path:&lt;/strong&gt; &lt;code&gt;GET /{key}&lt;/code&gt; looks up the metadata, then fetches the body &lt;strong&gt;cache-first&lt;/strong&gt; — from an &lt;strong&gt;in-memory cache&lt;/strong&gt; or &lt;strong&gt;CDN&lt;/strong&gt; on a hit, falling through to the blob store only on a miss — and serves it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache + CDN:&lt;/strong&gt; because a paste never changes after creation, it caches beautifully; hot pastes live in cache and at CDN edges, so the blob store sees only misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stateless servers:&lt;/strong&gt; the app tier holds no session state, so it scales horizontally behind the load balancer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expiry sweeper:&lt;/strong&gt; a background job (or native store TTL) removes pastes whose &lt;code&gt;expires_at&lt;/code&gt; has passed, keeping storage bounded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Say explicitly that the &lt;strong&gt;small metadata and the large blob are stored separately&lt;/strong&gt;, and that reads are &lt;strong&gt;cache-first&lt;/strong&gt; because a paste is immutable. Naming that split — key-gen plus metadata plus blob, with a cache in front — is a signal the interviewer is listening for.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7f22jez3jnp245h8g1xf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7f22jez3jnp245h8g1xf.jpeg" alt="A Pastebin architecture diagram showing a write path where a client request hits stateless app servers behind a load balancer, which generate a short key via a key-generation service, store the paste body in a blob object store and the metadata in a datastore, then return the key; and a read path where a request looks up metadata by key and fetches the body cache-first from an in-memory cache or CDN, falling through to the blob store on a miss, with a background expiry sweeper removing expired pastes." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep dive: generating the short, unique key
&lt;/h2&gt;

&lt;p&gt;This is the first decision, and it is the half Pastebin shares with a &lt;strong&gt;URL shortener&lt;/strong&gt; — so name that kinship, get it right, and keep it tight rather than re-deriving it for ten minutes. The follow-up is about collisions: &lt;em&gt;how do two app servers both hand out keys without ever clashing?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You need a key that is &lt;strong&gt;short&lt;/strong&gt;, &lt;strong&gt;URL-safe&lt;/strong&gt;, and &lt;strong&gt;collision-free&lt;/strong&gt;. The common approaches, as tradeoffs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it optimises / costs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Counter + base62&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a key-generation service (or a distributed counter) hands each app server a &lt;strong&gt;range&lt;/strong&gt; of numbers; encode each in &lt;strong&gt;base62&lt;/strong&gt; (&lt;code&gt;A–Z a–z 0–9&lt;/code&gt;). Guaranteed unique with &lt;strong&gt;no collision check&lt;/strong&gt;, and ~7 chars covers a huge keyspace — the clean default.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Random + collision check&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;generate N random URL-safe characters and check the datastore for a clash, retrying on the rare hit. Simple and unguessable, but needs a read before every write.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hash + truncate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;hash the content (plus a salt/time) and take a prefix. No coordination, but collisions must still be handled and identical content can map to the same hash.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Reach for &lt;strong&gt;counter + base62&lt;/strong&gt;: pre-allocating ranges to each writer means two servers never pick the same key and neither needs to check for collisions, which keeps the write path fast. Base62 keeps the key short and safe to drop straight into a URL. If the requirement is that unlisted pastes be &lt;strong&gt;unguessable&lt;/strong&gt;, lean toward a &lt;strong&gt;random&lt;/strong&gt; key (or add random bits) so the next key can't be inferred from the last. Saying out loud that this is the same core problem as a URL shortener — and that the interesting part of &lt;em&gt;Pastebin&lt;/em&gt; is the storage, not the key — shows you know where to spend your minutes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do two app servers both hand out keys without ever colliding?" I don't let them pick freely. A key-generation service hands each server a pre-allocated range of numbers, and each is encoded in base62, so two servers draw from disjoint ranges and can never produce the same key — no collision check needed on the write path. If I also need unlisted pastes to be unguessable, I add random bits so the next key can't be inferred from the last.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Storage &amp;amp; files · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Design Pastebin out loud, with follow-ups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: storing the paste — metadata vs blob, and read-heavy serving
&lt;/h2&gt;

&lt;p&gt;This is the decision that makes Pastebin more than a shortener, and the one the round is really about. The follow-up is blunt: &lt;em&gt;someone pastes a megabyte of logs — where does that go, and why not in the database?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is that the &lt;strong&gt;big body does not belong in your metadata database&lt;/strong&gt;. Row stores are built for small, structured, queryable records — not for multi-megabyte opaque blobs, which bloat the rows, slow every query, and waste the database's expensive strengths on bytes it never needs to index. So you split storage by shape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata in a datastore:&lt;/strong&gt; &lt;code&gt;key → blob_location, created_at, expires_at, size, visibility&lt;/code&gt; — small, indexed by the short key, cheap to query. This is what the read path hits first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body in blob / object storage:&lt;/strong&gt; the raw text sits in an &lt;strong&gt;object store&lt;/strong&gt; (S3-style) at &lt;code&gt;blob_location&lt;/code&gt;, which is built for large, cheap, durable, write-once objects. The metadata points to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read is metadata-then-blob, cache-first:&lt;/strong&gt; &lt;code&gt;GET /{key}&lt;/code&gt; looks up the metadata, then fetches the body — served from an &lt;strong&gt;in-memory cache&lt;/strong&gt; or a &lt;strong&gt;CDN&lt;/strong&gt; on a hit, and from the blob store only on a miss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Immutable means cacheable:&lt;/strong&gt; a paste never changes after it is written, so it can be cached aggressively and sit at CDN edges indefinitely (until it expires) — exactly what makes a read-heavy system cheap.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The point to state plainly: separating the &lt;strong&gt;small, queryable metadata&lt;/strong&gt; from the &lt;strong&gt;large, opaque blob&lt;/strong&gt; is the Pastebin-specific move, and it is what lets reads scale through a cache and CDN while the database stays lean. A URL shortener never needs this because its "body" is a single short string; Pastebin does, because its body is content.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Someone pastes a megabyte of logs — where does that go, and why not in the database?" Not in the metadata database — a row store is for small, queryable records, and a multi-megabyte blob would bloat the rows and slow every query. The body goes to blob / object storage, which is built for large, cheap, durable objects, and the metadata just holds a pointer to it. The read path looks up metadata by key, then serves the body cache-first from a CDN — and because a paste never changes, it caches beautifully.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsk9sp1n4pbfdvcj3mruf.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsk9sp1n4pbfdvcj3mruf.jpeg" alt="A storage diagram contrasting a small metadata datastore keyed by paste id that holds the key, blob location, timestamps, size and visibility, against a blob object store that holds the large paste body, with a read path that looks up metadata by key then fetches the body cache-first from an in-memory cache and CDN, falling through to the blob store only on a miss." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;span&gt;System Design&lt;/span&gt;&lt;br&gt;
&lt;span&gt;HLD · Storage &amp;amp; files · 45 min&lt;/span&gt;&lt;br&gt;
&lt;strong&gt;Defend the metadata-vs-blob storage split out loud&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise on Zynter →&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;


&lt;h2&gt;
  
  
  Deep dive: expiration, TTL and keeping it clean
&lt;/h2&gt;

&lt;p&gt;Storage decides where a paste lives; this decides when it leaves — and it is the piece that keeps the whole system cheap. The follow-up is about the mechanics: &lt;em&gt;a paste is set to expire in an hour — what actually deletes it, and what does a reader see after?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every paste carries an &lt;code&gt;expires_at&lt;/code&gt;, set by the user or a default. You enforce it with &lt;strong&gt;two mechanisms working together&lt;/strong&gt;, because either alone has a gap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lazy deletion on read:&lt;/strong&gt; when a &lt;code&gt;get_paste&lt;/code&gt; arrives, check &lt;code&gt;expires_at&lt;/code&gt;; if it has passed, treat the paste as gone — return a &lt;strong&gt;404&lt;/strong&gt; and don't serve stale content. This guarantees correctness instantly, even before anything is physically removed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background cleanup (or native TTL):&lt;/strong&gt; a sweeper job periodically deletes expired pastes from the blob store and metadata datastore — or you set a native &lt;strong&gt;TTL&lt;/strong&gt; on the records so the store evicts them for you. This reclaims space so storage stays bounded, which lazy deletion alone would not do (an un-read expired paste would linger forever).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why both:&lt;/strong&gt; lazy deletion keeps &lt;em&gt;reads correct&lt;/em&gt; the instant a paste expires; the cleanup job keeps &lt;em&gt;storage bounded&lt;/em&gt; over time. Together they mean a reader never sees expired content and you never pay to store it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While you are here, name the cheap, mature guards the interviewer wants to hear: pastes can be &lt;strong&gt;public, unlisted, or private&lt;/strong&gt;, and a &lt;strong&gt;hard-to-guess key&lt;/strong&gt; makes an unlisted paste effectively unreachable without the link; and the write path is &lt;strong&gt;rate-limited&lt;/strong&gt; so the service can't be flooded with spam pastes. These are the "sound choices" that show judgement without over-building.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A paste is set to expire in an hour — what actually deletes it, and what does a reader see after?" Two things working together. On read, I check &lt;code&gt;expires_at&lt;/code&gt; and return a 404 the moment it has passed, so no one ever sees expired content — that's lazy deletion. Separately, a background cleanup job (or a native TTL on the store) physically removes expired pastes from the blob and metadata stores so the footprint stays bounded. Lazy deletion keeps reads correct instantly; the sweeper keeps storage cheap over time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5d1ejz9mp0huupmlllu.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm5d1ejz9mp0huupmlllu.jpeg" alt="An expiration diagram showing each paste carrying an expires_at timestamp, enforced two ways together: lazy deletion where a read after expiry returns a 404 and marks the paste gone, and a background cleanup job or native store TTL that physically removes expired pastes so storage stays bounded, with a note that a hard-to-guess key keeps unlisted pastes private and rate limiting guards the write path against abuse." width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The follow-ups that decide the Pastebin round
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"How is this different from a URL shortener?"&lt;/strong&gt; → Same key-generation half, but Pastebin stores and serves arbitrary-size content that expires: the body goes to blob storage separate from the metadata, reads are cache-first, and everything has a TTL. (problem solving)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How do you generate a unique key without collisions?"&lt;/strong&gt; → A key-generation service hands each writer a pre-allocated range encoded in base62, so two servers never collide and no collision check is needed; add random bits if unlisted pastes must be unguessable. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Where does the paste body live, and why not the database?"&lt;/strong&gt; → In blob / object storage, which is built for large, cheap, durable objects; the metadata database only holds a small pointer plus timestamps, so queries stay fast and the big content caches well. (communication)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"It's read-heavy — how do you scale reads?"&lt;/strong&gt; → Cache-first: an in-memory cache and a CDN serve hot pastes, and since a paste is immutable it caches aggressively; the blob store only sees misses, and app servers are stateless behind a load balancer. (technical execution)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How do pastes expire without storage growing forever?"&lt;/strong&gt; → Lazy deletion returns a 404 on read the instant &lt;code&gt;expires_at&lt;/code&gt; passes, and a background cleanup job or native store TTL physically removes expired pastes, so reads stay correct and the footprint stays bounded. (problem solving)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to prepare: a one-week plan
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1–2:&lt;/strong&gt; Learn the five-step framework until it is reflex. Do Pastebin end to end on paper — requirements, estimate, API, data model, and the write-path/read-path boxes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 3–4:&lt;/strong&gt; Drill the key and the storage split: counter + base62 keys, and why the body goes to blob storage while metadata stays in the datastore, served cache-first. Say each tradeoff in one sentence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 5–6:&lt;/strong&gt; Own expiry and read scaling — lazy deletion plus a cleanup job, the cache and CDN, and the public/unlisted/private + rate-limiting guards. Answer each of the five follow-ups out loud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 7:&lt;/strong&gt; A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post is the deep walkthrough behind one row of the broader &lt;a href="https://app.zynter.ai/blogs" rel="noopener noreferrer"&gt;Top 25 system design interview questions&lt;/a&gt; — once Pastebin is automatic, the same spine carries the rest of the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I design Pastebin step by step?
&lt;/h3&gt;

&lt;p&gt;Follow the five-step HLD framework. Clarify requirements (paste text and get a short unique link; open the link to read the content; set an expiry; public / unlisted / private); estimate to show it is read-heavy, that a paste can be large, and that expiry keeps storage bounded; define the small API (create_paste, get_paste, delete_paste) with the key split — small metadata in a datastore, the large body in blob storage; draw the design (stateless app servers behind a load balancer; a write path that mints a key, stores the body in the blob store and the metadata in the datastore; a read-heavy path that looks up metadata and serves the body cache-first from a cache or CDN; an expiry sweeper); then deep-dive the key generation, the metadata-vs-blob storage, and expiration. Finish on the follow-ups about collisions and where the body lives.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does Pastebin generate a unique key for each paste?
&lt;/h3&gt;

&lt;p&gt;With a short, URL-safe, collision-free key — most cleanly a counter plus base62. A key-generation service (or distributed counter) hands each app server a pre-allocated range of numbers, and each number is encoded in base62 (A–Z, a–z, 0–9), so two servers draw from disjoint ranges and can never produce the same key, with no collision check needed on the write path; roughly seven base62 characters already cover a huge keyspace. Alternatives are generating a random URL-safe string and checking the datastore for a clash, or hashing the content and truncating. If unlisted pastes must be unguessable, use random keys (or add random bits) so the next key can't be inferred from the last. It is the same key problem as a URL shortener.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where is the paste content stored — the database or object storage?
&lt;/h3&gt;

&lt;p&gt;The large paste body goes in blob / object storage, not the metadata database. A relational or row store is built for small, structured, queryable records, and a multi-megabyte blob would bloat the rows and slow every query. So the body lives in an object store (S3-style) that is built for large, cheap, durable, write-once objects, and the metadata database holds only a small record — the key, the blob's location, timestamps, size and visibility — keyed by the short key. The read path looks up the metadata, then fetches the body cache-first. Separating the small, queryable metadata from the large, opaque blob is the move that distinguishes Pastebin from a URL shortener.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do you scale Pastebin for read-heavy traffic?
&lt;/h3&gt;

&lt;p&gt;By making reads cache-first, which works because a paste is immutable once written. Hot pastes are served from an in-memory cache and from a CDN at the edge, so the same shared link can be opened thousands of times without touching the blob store, which only sees cache misses. App servers are stateless behind a load balancer so they scale horizontally, and the metadata datastore and blob store are read-replicated. Writes are modest and do not need the same machinery. Because content never changes after creation, aggressive caching is safe and cheap — exactly what a read-heavy, write-once workload wants.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does paste expiration and TTL cleanup work?
&lt;/h3&gt;

&lt;p&gt;Each paste stores an expires_at timestamp, and you enforce it two ways at once. Lazy deletion handles correctness: when a read arrives, you check expires_at and return a 404 the moment it has passed, so a reader never sees expired content even before anything is physically removed. A background cleanup job — or a native TTL on the blob store and datastore — handles space: it periodically deletes expired pastes so storage stays bounded, which lazy deletion alone would not do because an un-read expired paste would linger forever. Together they keep reads correct instantly and storage cheap over time, so the active footprint is only a rolling window of pastes rather than the whole history.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I practise a system design interview out loud?
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practise it on Zynter
&lt;/h2&gt;

&lt;h3&gt;You can read why the body goes to blob storage. The round is won on saying it under follow-ups.&lt;/h3&gt;

&lt;p&gt;Pick the Pastebin round, draw the write-path and read-path design on the canvas, and defend it out loud to a voice interviewer that keeps asking "how is this different from a URL shortener?" and "where does a megabyte of pasted text go?" — then read the debrief. &lt;a href="https://app.zynter.ai" rel="noopener noreferrer"&gt;Zynter.ai&lt;/a&gt; is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.zynter.ai/interview/system-design/hld" rel="noopener noreferrer"&gt;Practise the Pastebin round →&lt;/a&gt;&lt;br&gt;
&lt;a href="https://app.zynter.ai/interview" rel="noopener noreferrer"&gt;Browse all interview tracks&lt;/a&gt;&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>interview</category>
      <category>dataengineering</category>
      <category>sql</category>
    </item>
  </channel>
</rss>
