System Design · step by step

Design a Key-Value Store

Step 1 / 9
The numbers to beat1/Nkeys moved on changevnodeseven spreadO(1)key → node

In the interview room

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

Functional requirements

What it must do — agree on these before drawing a single box.

  • Get / Put: put(key, value) and get(key) — a giant dictionary, no SQL, no joins.
  • Any node serves: a client hits any node, which coordinates the request — no special master.
  • Survive failure: a key stays available and durable through node deaths.
  • Tunable consistency: callers dial between fast and strongly-consistent per workload.
  • Reconcile conflicts: concurrent writes to a key during a partition are detected, not silently lost.

Non-functional requirements

The qualities that shape the whole design — each one names the mechanism that buys it.

Never go down at scale (availability first)
A masterless, symmetric design — every node runs the same code and can coordinate, so a dead node is routine, not an outage.
Minimal reshuffle when nodes come and go
A consistent-hash ring: adding or removing a node moves only ~1/N of keys, and virtual nodes spread that load across many survivors.
Durability through node loss
Replicate each key to the next N nodes on the ring (N=3), so it survives up to two simultaneous failures and reads spread across replicas.
Read-your-writes when you need it
Quorum with R + W > N makes every read set overlap the last write set — turn the knob toward consistency or latency per workload.
Always writable, even mid-failure
Hinted handoff lets a stand-in accept a write and replay it later; read repair and Merkle anti-entropy converge replicas afterward.
Decentralized membership
Nodes gossip health and ring state peer-to-peer — no central registry to become a bottleneck or single point of failure.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Consistent-hash ringover hash(key) % N

Modulo hashing remaps almost every key when the node count changes — a catastrophic reshuffle in a churning cluster. The ring moves only the keys between a node and its neighbor.

Quorum R + W > Nover wait-for-all or trust-one

Waiting for all replicas lets one slow node stall every request; trusting one risks stale reads. R + W > N guarantees the read set overlaps the last write — consistency you can tune.

Vector clocksover last-write-wins

LWW by wall-clock is simple but silently drops a real concurrent update, and clock skew makes “last” unreliable. Vector clocks capture causality, surfacing true conflicts for the app to merge — carts merge, counters add.

AP — availableover CP — strictly consistent

The store’s whole promise is “never down.” Under a partition CAP forces a choice; a Dynamo-style store favors availability and reconciles later. Need strict consistency? Raise R + W > N — it’s a dial, not a law.

Hinted handoffover rejecting the write

Rejecting writes whenever a replica is down means writes fail constantly at scale. A stand-in holds a hint and replays it when the rightful replica returns — availability now, consistency soon.

What this teaches

Learn system design by building a distributed key-value store like DynamoDB or Cassandra step by step. An interactive guide covering consistent hashing, replication, quorum reads/writes, conflict resolution, gossip membership, and hinted handoff.

Key takeaways

  • Any node coordinates — no master, no single point of failure.
  • Consistent hashing maps keys to nodes; only 1/N keys move on change.
  • Each key replicated to the next N nodes on the ring for durability.
  • Quorum reads/writes with R + W > N give tunable consistency.
  • Vector clocks detect and resolve conflicting concurrent writes.
  • Gossip spreads membership and ring state with no central registry.
  • Hinted handoff and read repair keep replicas converging through failures.

Concepts covered

  • What is a key-value store?
  • Any node, one value
  • Consistent hashing
  • Replication
  • Quorum reads & writes
  • Resolving conflicts
  • Gossip membership
  • Hinted handoff & read repair
▶  Watch it explained

Prefer a video walkthrough?

Design a Key-Value Store (like DynamoDB) — read the full walkthrough as text

the same steps, decisions & trade-offs, for reading, reference & search

The big idea

What is a key-value store?

Sometimes you don’t need SQL — you need a giant, always-on dictionary: put(key, value) and get(key), spread across hundreds of machines, that never goes down even when machines do. A single database can’t offer that.

Build a distributed, replicated store with no single master. Spread keys across many nodes, keep several copies of each, and let any node serve a request. The hard part isn’t the dictionary — it’s staying available and consistent while machines fail.

How to read this: We add one piece at a time, problem then fix. This is the Dynamo lineage (DynamoDB, Cassandra, Riak). Watch it grow. Hit Begin.

Step 1 · The skeleton

Any node, one value

A client wants to put and get a key without knowing or caring which of hundreds of machines actually holds it. Routing every request through one master would just recreate the single point of failure we’re trying to avoid. So how does a request find its key?

Design decision: A client must put/get a key across hundreds of machines, and it must never go down. Routing?

The call: Let the client hit any node, which coordinates that request. — Every node runs the same code and can locate replicas and serve the request. No special master means no single failure stops the world — symmetry is the whole philosophy.

Let the client hit any node, which acts as the coordinator for that request: it finds the right home for the key and reads/writes there. Every node can coordinate, so there’s no special, fragile master.

Decentralized by design: Symmetry is the whole philosophy here: all nodes run the same code and play the same role. No master means no single thing whose failure stops the world.

Step 2 · Where does a key live?

Consistent hashing

With hash(key) % N, changing the number of nodes remaps almost every key — a catastrophic reshuffle every time you add or lose a machine. At scale, nodes change constantly.

Design decision: Nodes are added and lost constantly. How do you map keys to nodes?

The call: A consistent-hash ring; a key goes to the next node clockwise. — Adding or removing a node moves only the keys between it and its neighbor — about 1/N of the data, not all of it. Virtual nodes spread that load across many survivors.

Place nodes and keys on a hash ring. A key belongs to the first node clockwise from its hash. Add or remove a node and only the keys between it and its neighbor move — a tiny fraction, not the whole dataset.

Virtual nodes for balance: Give each physical node many points on the ring (vnodes) so load spreads evenly and a departing node’s keys scatter across many survivors instead of dumping onto one neighbor.

Step 3 · Survive a death

Replication

If a key lives on exactly one node, that node’s failure means the key is gone and unreachable. Disks and machines fail constantly at scale — single copies are not an option.

Design decision: Disks and machines fail constantly. Where do you keep each key?

The call: Replicate each key to the next N nodes on the ring. — Three consecutive ring nodes (N=3) each hold a copy, so the key survives up to two failures and reads can hit the healthiest replica. The preference list skips vnode duplicates so copies land on distinct machines.

Store each key on the N nodes following its position on the ring (its “preference list”). With N=3, three consecutive nodes each hold a copy, so the key survives failures and can be read from whichever replica is closest or healthiest.

The preference list: Replicas are just the next N distinct nodes clockwise. Skipping virtual duplicates ensures the copies sit on different physical machines (and ideally different racks).

Step 4 · Consistent or available?

Quorum reads & writes

With three copies, who’s the source of truth? Waiting for all replicas means one slow node stalls every request; trusting one risks reading stale data right after a write. CAP says you can’t have perfect consistency and availability under partitions.

Design decision: With 3 copies, how many do you wait for on a read and a write?

The call: Pick R and W with R + W > N (e.g. 2/2). — When the read set and write set must overlap, every read sees the last write. R=W=2 on N=3 gives strong consistency with one-node fault tolerance — and you can re-tune per workload.

Use a quorum: a write succeeds after W replicas ack; a read consults R. Choose R + W > N and every read set overlaps the last write set — so you always see the latest value. Tune R and W to slide between fast and strongly-consistent.

R + W > N: With N=3, (W=2, R=2) gives strong consistency with one-node fault tolerance. (W=1) favors fast writes; (R=1) favors fast reads. The knobs are yours, per workload.

Step 5 · Two truths

Resolving conflicts

During a network partition, two replicas can each accept a write to the same key. When the partition heals, they disagree — and with W < N this is expected, not a bug. Which value wins?

Design decision: A partition lets two replicas each accept a write to the same key. On heal, who wins?

The call: Version writes; use vector clocks to detect true conflicts. — Vector clocks capture causality, so the system distinguishes a newer write from genuine divergence and can surface both siblings for the app (or a merge function) to reconcile — carts merge, counters add.

Attach version metadata to every write. Last-write-wins (by timestamp) is simple but can drop data. Vector clocks capture causality, detecting true conflicts and surfacing both versions (siblings) for the application — or a merge function — to reconcile.

Make conflicts explicit: In an available system, conflicts will happen. The choice is whether to silently lose data (LWW) or to detect divergence and let the app decide (vector clocks). Carts merge; counters add.

Step 6 · Who’s alive?

Gossip membership

Coordinators need to know which nodes are up and who owns which ring range — but a central membership registry would be another single point of failure and a bottleneck.

Nodes gossip: each periodically exchanges health and ring state with a few random peers. Within seconds the whole cluster converges on a shared view of membership — fully decentralized, no coordinator required.

Epidemic protocols: Information spreads like a rumor — exponentially and resiliently. Gossip needs no central authority and degrades gracefully: a few lost messages just slow convergence slightly.

Step 7 · Heal while degraded

Hinted handoff & read repair

If a target replica is temporarily down during a write, do you reject the write (hurting availability) or accept it and risk that replica permanently missing the update?

Design decision: A target replica is down during a write. Reject the write, or accept it?

The call: Accept it; a healthy node holds a hint and replays it later. — Hinted handoff keeps writes always-succeeding: a stand-in node stores the write and forwards it when the rightful replica returns. Read repair and Merkle-tree anti-entropy converge the rest.

Accept it: a healthy node stores a hint and replays the write to the rightful replica once it returns (hinted handoff). Meanwhile, reads that notice a stale replica push the fresh value back to it (read repair), and background anti-entropy (Merkle trees) reconciles the rest.

Always writable, eventually consistent: These mechanisms let writes always succeed and let replicas quietly converge afterward. Availability now, consistency soon — the core bargain of a Dynamo-style store.

You did it

You just designed a key-value store.

  • Any node coordinates — no master, no single point of failure.
  • Consistent hashing maps keys to nodes; only 1/N keys move on change.
  • Each key replicated to the next N nodes on the ring for durability.
  • Quorum reads/writes with R + W > N give tunable consistency.
  • Vector clocks detect and resolve conflicting concurrent writes.
  • Gossip spreads membership and ring state with no central registry.
  • Hinted handoff and read repair keep replicas converging through failures.
RUN IT YOURSELF

Consistent hashing, in Python & TypeScript

A distributed KV store decides which node owns each key with consistent hashing. Here is the ring in both languages, running live. Switch tabs, read the comments, edit the nodes, and hit Run.

HOW TO READ THE CODE — 4 IDEAS
  1. Hash both nodes and keys onto a ring (here, 0–359 degrees).
  2. A key belongs to the first node clockwise from its position (steps 2–3).
  3. Adding or removing a node only moves the keys between it and its neighbour — not everything.
  4. Contrast plain hash(key) % N, where changing N reshuffles every key.
CPython · WebAssembly
built to be reasoned about, not memorized — make the calls, drop a replica, run the gauntlet.
Finished this one? 0 / 62 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.