This gives away every stage. For a clean attempt, practise it first, then come back.
The short version
- With about 110 machines, something is always broken. Failure handling is the normal case, not an edge case.
- Consistent hashing with virtual nodes decides where each key lives, and moves little data when machines come and go.
- Three copies, and quorums (W + R > N) let each request trade speed for freshness.
- To stay writable when a replica is down, write to a stand-in and hand the data over later.
- Copies drift, so repair them: on reads, with handoffs, and with background comparisons.
The 45 minutes
Before we start
In every other interview question, you say “store it in a key-value store like DynamoDB” and move on. This time, you’re building the store.
The interface is tiny: put a value under a key, get it back, delete it. Everything interesting is underneath. The data is too big for one machine, so it must be spread out. Machines fail every day, so there must be copies. Copies can disagree, so you need rules for who wins. And all of it must answer in a few milliseconds.
This design follows Amazon’s Dynamo paper, the idea behind DynamoDB and Cassandra. You don’t need to have read it; we’ll build it step by step.
Same order as always: requirements, quick maths, data and API, a design that grows, then deep dives.
Minutes 0–5Requirements
Interviewer: Design a distributed key-value store, something like DynamoDB or Cassandra.
You: What operations do we need: get, put, delete? Range queries?
Interviewer: Get, put and delete by key. No range queries or transactions.
You: How much data, how big are values, and how much traffic?
Interviewer: About 100 TB, values usually around 10 KB, up to 1 MB. About a million operations a second.
You: When machines or the network fail, should we keep accepting writes, or refuse them to stay perfectly consistent?
Interviewer: Stay available. Think of a shopping cart: always let people add items, and sort out disagreements later.
You: Can clients ask for more up-to-date reads when they need them?
Interviewer: Yes, letting them choose per request would be good.
You: How many copies, and where?
Interviewer: Three copies, in three zones of one region.
You: And we need to add machines without downtime?
Interviewer: Yes.
Functional requirements
- Put, get and delete values by key.
- Let each request choose between faster and more up to date.
- Add or remove machines without downtime, moving as little data as possible.
Nice to have: values that expire after a set time. Out of scope: range queries, secondary indexes and transactions.
Non-functional requirements
- Fast: a few milliseconds per request, even at the 99th percentile.
- Always writable: writes keep working when machines or network links fail.
- Durable: three copies in three zones, so even losing a whole zone loses nothing.
- Big: 100 TB of data and a million operations a second.
- Copies catch up: after failures, all copies eventually agree again.
Minutes 5–10Back-of-the-envelope estimates
You: 100 TB with three copies is 300 TB stored. If each machine has 4 TB of usable disk and we keep disks about 70% full, that’s roughly 110 machines.
You: A million operations a second, each touching three copies, spread over 110 machines, is about 27,000 a second per machine. Comfortable.
You: At 10 KB per value, 100 TB is around 10 billion keys.
Two things follow. First, with 110 machines, one of them is failing or being updated almost every day. Failures aren’t rare edge cases here; they’re the normal state. Second, with 10 billion keys, we can’t keep a central list of where each one lives. The location has to be worked out from the key itself.
Minutes 10–15Core entities and the API
Core entities
- Key and value: the key is a string; the value is opaque bytes up to 1 MB.
- Version: a small record attached to each value, used to tell which of two copies is newer.
- Node: one machine, holding part of the data and part of the copies.
- Ring: how keys are assigned to nodes (explained below), shared by every node.
The API
/v1/kv/cart:user_42Store a value. W says how many copies must confirm before replying.
{ "items": ["book", "pen"] }- 200
{ "version": "v7" }
/v1/kv/cart:user_42Read a value. R says how many copies to ask.
- 200
{ "value": { "items": ["book", "pen"] }, "version": "v7" } - 404No such key.
/v1/kv/cart:user_42Delete a key. Stored as a “deleted” marker, so stale copies can’t bring it back.
- 204Deleted.
The two small numbers, W and R, are how each request picks “faster” or “more up to date”. We’ll see exactly what they mean in stage 3.
Minutes 15–25The design, built up
Stage 1One machine: a hash map, a log, and snapshots
You: With no scale at all: one server keeps a hash map of key to value in memory. Every write is first appended to a log file on disk, so after a crash we can replay the log and rebuild the map. Every so often we save a full snapshot so the log doesn’t grow forever.
This is fast and simple, and plenty of real systems start here. But 100 TB won’t fit on one machine, and when it crashes or reboots, everything is unavailable. Both break our requirements.
Stage 2Spread keys across machines with consistent hashing
You: First, split the data. The obvious way is hash the key and take it modulo the number of machines. But then adding one machine changes the answer for almost every key, and nearly all the data has to move.
You: Instead, consistent hashing: imagine a circle. Each machine is placed at points on the circle by hashing its name, and each key is placed by hashing the key. A key belongs to the next machine clockwise. Adding a machine only takes keys from its neighbours.
You: And with virtual nodes, each machine appears at many points around the circle, say 256. That spreads load evenly and means a joining machine takes small slices from many machines instead of a big chunk from one.
Lesson 5.2 shows how much data moves each way:
Stage 3Three copies and quorums
You: Next, copies. Each key is stored on the first three distinct machines clockwise from it, and we make sure they’re in three different zones.
You: For a write, the coordinator sends it to all three and replies as soon as W of them confirm. For a read, it asks R of them and returns the newest version.
You: If W plus R is more than 3, every read overlaps with every write on at least one copy, so reads see the latest successful write. W = 2 and R = 2 is the balanced default. W = 1, R = 1 is fastest, but might return slightly old data. Each request can choose.
That overlap rule is easier to see than to explain. Lesson 5.3 has a demo:
Stage 4When machines fail: gossip, hinted handoff, repair
You: Now failures, which we said are normal. Four pieces.
You: Gossip: every second, each node chats with a few random others, sharing who’s up and the ring layout. News spreads to the whole cluster in seconds, with no central coordinator to fail.
You: Hinted handoff: if a replica is down, the coordinator writes to the next healthy node instead, with a note saying who the data really belongs to. When the replica comes back, the data is handed over. So writes never fail just because one machine is rebooting.
You: Repair: when a read sees copies disagree, it updates the stale one. And in the background, replicas compare their data to find and fix anything that drifted.
You: On each node, data lives in a write-friendly storage engine: a log, an in-memory table, and sorted files on disk.
Here’s hinted handoff, compared with simply waiting for all three:
Checking the design against the requirements
| Requirement | Where the design handles it |
|---|---|
| Put, get, delete | Coordinator routes each request to the key’s three replicas |
| Choose faster or more up to date | W and R per request |
| Add or remove machines without downtime | Consistent hashing with virtual nodes; small slices move |
| Fast | No central coordinator; one hop to replicas; write-friendly storage |
| Always writable | Stand-in nodes and hinted handoff when a replica is down |
| Durable | Three copies in three zones; every write logged first |
| Big | About 110 machines, each doing ~27,000 operations a second |
| Copies catch up | Read repair, hinted handoff, background comparison |
Deep diveWhere does each key live?
Interviewer: Why not just hash mod N?
Bad solutionHash mod N
hash(key) % 110. Simple, until machine 111 arrives and almost every key now maps somewhere else. Nearly all 300 TB would have to move, and while it does, reads land on the wrong machines.
Good solutionConsistent hashing
Keys and machines on a circle; each key goes to the next machine clockwise. A new machine only takes keys from its immediate neighbour, about 1/110th of the data.
Great solutionConsistent hashing with virtual nodes
Each machine appears at, say, 256 points around the circle. Data spreads evenly even with uneven luck in hashing, a new machine takes small slices from many machines at once (so no single machine is overloaded moving data), and more powerful machines can simply be given more points. Lesson 5.2 shows data moving gradually instead of all at once:
Deep diveWhen copies disagree
Interviewer: Two clients update the same cart at the same time, through different replicas. Which value wins?
Bad solutionLast write wins, by the clock
Keep whichever value has the newest timestamp. Simple, but machine clocks drift, so the write that really happened second can lose, and the lost write disappears without anyone noticing. For a shopping cart, an item silently vanishes.
Good solutionVersion vectors
Each value carries a small record of which replicas have updated it and how many times. Comparing two records tells you whether one version came after the other (keep the newer one) or whether they happened at the same time without knowing about each other. In that case, keep both and let the application merge them. Amazon’s Dynamo did exactly this for carts: when two versions conflict, merge them by keeping the items from both.
Great solutionPick per use case
For some data, last write wins is genuinely fine, like “last known location”. For carts, use version vectors and merge. For counters and sets, use data types designed to merge automatically, called CRDTs. The strong answer is naming what each choice can lose. Lesson 5.3 shows last write wins losing data:
Deep diveCopies drifting apart
Interviewer: After all these failures, how do you make sure the three copies eventually match?
You: Three layers, from cheapest to most thorough.
You: Read repair: whenever a read sees replicas disagree, it sends the newest version to the stale ones. Popular keys get fixed constantly, for free.
You: Hinted handoff: writes held by a stand-in are delivered when the real replica returns.
You: Anti-entropy: in the background, replicas compare their data using Merkle trees, which are trees of hashes over ranges of keys. If two replicas’ top hashes match, everything matches. If not, they walk down the tree to find exactly which keys differ, without sending all the data across the network.
One subtle trap: deletes. If a replica missed a delete, it still has the old value, and read repair would copy it back. So a delete is stored as a marker (a “tombstone”), kept long enough for all copies to see it, and only then cleaned away.
Deep diveInside each node
Interviewer: What does a single node use to store data on disk?
You: A write-friendly design. Every write is appended to a log on disk first, so nothing is lost in a crash. Then it goes into a sorted table in memory. When that fills up, it’s written out as a sorted file, and background work merges files together over time.
You: Reads check memory first, then the files from newest to oldest, and use a Bloom filter per file to skip files that can’t contain the key.
You: This is the LSM-tree design from lesson 3.2, used by Cassandra and RocksDB. It suits us because writes are just appends, which disks handle very fast.
Deep diveThe CAP choice, made concrete
Interviewer: You keep saying “always writable”. What does that cost?
You: During a network split, some replicas can’t reach others. We choose to keep accepting writes on both sides, using stand-ins if needed. The cost is that, for a while, the two sides may hold different versions, and a read might return an older one. When the split heals, repair and conflict resolution bring them back together.
You: The other choice would refuse writes on the side without a majority. That’s what systems built on consensus do, like etcd. Our interviewer chose availability, the shopping-cart choice, so we accept temporary disagreement in exchange for never saying no.
Lesson 5.4 shows a network split playing out both ways:
The complete architecture
Here’s everything together: the full design from the last stage, with every part we added along the way. If you only remember one picture from this question, make it this one.
How one request travels through it:
- The client library knows the ring, so it sends a request straight to a node responsible for the key. That node coordinates the request.
- The coordinator hashes the key to find its three replicas: the next three distinct machines around the ring, placed in three different zones.
- For a write, it sends the value to all three and replies once W of them (say 2) confirm. Each replica appends the write to its log first, then its in-memory table.
- For a read, it asks R of them (say 2) and returns the newest version. If one replica had an older version, it quietly updates it (read repair).
- If a replica is down, another node accepts the write with a note saying who it’s for, and hands it over when the replica comes back.
- Nodes gossip to share who’s up and how the ring is laid out, and a background process compares copies to fix anything that drifted.
How this answer gets scored
Interviewers don’t tick boxes, but their notes on this question tend to land on the same few moments. Here’s what each one sounds like from the other side of the table.
| Moment | What earns credit | What costs you |
|---|---|---|
| The first five minutes | Asking “accept writes or stay consistent?” | Promising both perfect consistency and availability |
| Placing keys | Consistent hashing with virtual nodes | Hash mod N |
| Copies | Three replicas across zones, W + R > N explained | “We’ll replicate it” with no detail |
| Conflicts | Comparing approaches and naming what each loses | Last write wins with no mention of clocks |
| Failures | Stand-ins, hinted handoff, read repair, Merkle trees | Writes failing whenever one machine is down |
| Deletes | Tombstones, so stale copies can’t bring data back | Deleting the value outright |
What the left column has in common: every choice points back to a requirement or a number from earlier. If you can say where each box came from, the interviewer will notice.
Stay in the loop
Get new problems and walkthroughs by email
One short email when something new is published: a deep-dive walkthrough, an LLD problem, or a real outage retold. No spam, unsubscribe any time.