Skip to the notes
JSGroundwork
JSGroundwork handwritten · web dev
✎Playground→⌘Problems↻Review🔥Progress

Chapters

24 chapters
⌕
Beginner7›
B1The mental modelB2Client-server basicsB3Vertical vs horizontalB4Databases in designB5Caching fundamentalsB6APIs & communicationB7Walkthrough: URL shortener
Intermediate9›
I1Load balancing in depthI2CDNsI3Message queues & asyncI4Consistency modelsI5Rate limiting & throttlingI6Designing for availabilityI7Storage systemsI8Search systemsI9Walkthrough: feed/chat
Advanced8›
A1CAP theorem in depthA2Sharding at scaleA3Distributed consensusA4Fault toleranceA5Observability at scaleA6Capacity estimationA7Case studiesA8Tradeoff thinking
/ search[ ] chaptert top

System Design levels

1Beginner2Intermediate3Advanced

Ready to read

JSJavaScript⑂Git◎Interview prepΣDSA in JSSDSystem Design
More topics15›
</>HTML{ }CSS⚛ReactNNext.jsNeNest.jsTSTypeScriptNoNode.js🐳DockerDBSQL & Databases✓Testing🔒Web Security☁Cloud & DevOps◈GraphQL◆Redis☸Kubernetes
100%
A1

The CAP theorem in depth

You never "pick two" — you pick one letter during a partition, and PACELC tells you what you picked for the other 99.9% of the time.

The version everyone repeats is wrong, and the wrongness matters

The folklore statement is "consistency, availability, partition tolerance — pick two." That framing fails the moment you take it seriously, because P is not a choice you get to make. A partition is a property of the network: a cut fibre, a failed top-of-rack switch, a bad BGP announcement, an AZ losing connectivity, or — most commonly — a node that is merely slow (a 40-second stop-the-world GC pause, a VM live-migration stall) and is therefore indistinguishable from a dead one. You cannot buy a network that never partitions, so "CA" is not a system design. It is a system with no story for partitions, which under partition quietly gives up both letters.

The precise statement is narrower and far more useful: when a partition occurs, and only then, you must choose between consistency and availability. When there is no partition — which is essentially all of the time — CAP says nothing whatsoever about your system. That last clause is the one that separates candidates: CAP is a theorem about the rare case, and the rare case is not where your latency budget lives.

client A node 1 node 2 client B partition — each side thinks the other died choose C minority refuses writes, client A gets an error choose A both sides accept writes, histories diverge — merge later
The choice only exists inside the red X. Notice that neither branch is "correct" — one costs you an error page, the other costs you a reconciliation problem you must have designed in advance.

The three letters, defined precisely (this is where marks are lost)

LetterWhat it formally meansWhat candidates think it means
CLinearizability. The system behaves as if there is exactly one copy of the data: every read returns the value of the most recently completed write, and once a read sees a value, no later read sees an older one.The C in ACID (integrity constraints). Unrelated.
AEvery request to a non-failing node returns a non-error response. No bound on how long it takes, and no promise the answer is fresh."Four nines of uptime." Also unrelated — a CP system can have a better SLA than an AP one.
PThe system keeps operating when arbitrary messages between nodes are dropped.An optional feature. It is not optional.

Two consequences fall straight out of those definitions. First, CAP-A has no latency bound, so a system that answers in 30 seconds is "available" — which is why CAP alone is useless for reasoning about production SLOs. Second, CAP-C is linearizability, the strongest single-object guarantee; weaker useful models (read-your-writes, monotonic reads, causal consistency) sit below it and are perfectly compatible with staying available during a partition. "AP" does not mean "no guarantees."

What CP actually looks like from the client's seat

Take a five-node leader-based cluster — etcd, ZooKeeper, a Spanner Paxos group, a MongoDB replica set with majority write concern. The network splits it 3 | 2.

  • Majority side (3 nodes): keeps or elects a leader, keeps committing writes, fully functional. If the old leader was on this side, there is no interruption at all.
  • Minority side (2 nodes): cannot reach a quorum, so it cannot commit anything. Writes fail immediately with something like "no leader" or a context deadline. Reads either fail too, or are served locally and are knowingly stale — that is a per-system decision, and a good one to ask about.
  • A client pinned to the minority sees hard errors for the entire duration of the partition, plus an election timeout on top (Raft's classic 150–300 ms, etcd's default 1000 ms, ZooKeeper tick-based failover typically a few seconds).

The client-visible contract is: never a wrong answer, sometimes no answer. That is the right trade whenever a wrong answer is more expensive than an error page — a ledger balance, decrementing the last unit of inventory, a cluster-membership record, a lock, a feature-flag kill switch.

What AP actually looks like from the client's seat

Now the same partition in Cassandra at consistency level ONE, or DynamoDB with eventually-consistent reads, or Riak. Both sides accept writes. Every client gets a 200. Nothing appears wrong — and that is precisely the danger, because the damage is deferred to reconciliation time. When the partition heals you have two divergent histories, and something has to merge them:

Reconciliation strategyWhat it costsReach for it when…
Last-write-wins by timestampSilently discards one side's writes. With wall-clock timestamps, clock skew of a few hundred ms decides which user's data survives.The value is a cache-like overwrite where losing an update is genuinely acceptable — a "last seen at" field, a presence flag.
Siblings / vector clocks returned to the appEvery read path must now handle "here are 3 conflicting values." Real complexity, pushed into product code.The app has domain knowledge that makes merging obvious (union the shopping cart).
CRDTs (G-Counter, OR-Set, LWW-Register)Restricted data types, metadata growth, and you must model the domain as a mergeable lattice.Counters, sets, collaborative text, presence — anywhere concurrent edits are normal (Redis CRDT, Automerge, Yjs).
Read repair + anti-entropy (Merkle trees)Background convergence only; does not decide semantics, just propagates whatever the winner already is.Always — it is a complement to one of the above, never a substitute.
⚠ "We'll go AP" is only half an answer The interviewer's next sentence is "so how do you merge?" If you cannot name last-write-wins, siblings, or a CRDT and say which one you'd use for this data, you have taken AP's availability without paying AP's price. "Eventually consistent" describes an outcome, not a mechanism. The canonical example is the Dynamo shopping cart: under LWW, a partition loses items a customer added; modelled as an OR-Set, the merge is a union and the worst case is a deleted item reappearing — which Amazon judged strictly better than a lost sale.

PACELC — the framing that actually predicts database behaviour

Abadi's 2010 extension is the one to bring up unprompted, because it covers the case CAP ignores: if there is a Partition, choose A or C; Else, choose Latency or Consistency. The "else" half applies over 99.9% of your system's life, and it is not a legal technicality — it is forced by physics. Keeping replicas linearizable requires a round trip to a quorum before you can answer, and round trips cost:

HopTypical RTTWhat that means for a quorum write
Same rack / same AZ0.2–0.5 msConsistency is nearly free; take it.
Cross-AZ, same region0.5–2 msStill cheap. This is why a 3-AZ quorum inside one region is the default shape for most CP systems.
us-east ↔ us-west60–70 msA cross-region quorum write costs at minimum the second-fastest RTT. Your write p50 now starts at ~35 ms.
us-east ↔ eu-west75–90 msGlobal linearizable writes are tens of milliseconds, permanently. No amount of engineering removes this.

Spanner is the clearest illustration of choosing consistency in the "else" branch and simply paying: it is PC/EC, it commits through Paxos across regions, and it adds a deliberate commit wait of roughly twice the TrueTime clock uncertainty (single-digit milliseconds) so that commit timestamps are globally meaningful. Writes land in the tens of milliseconds. In exchange you get external consistency across a planet, and read-only transactions at a past timestamp that any replica can serve locally with no coordination at all.

Where real systems sit — the honest, tunable version

SystemPACELCWhy, and where the knob is
Postgres, single primary + sync standbyPC/ECWith synchronous_commit = on and a named standby, a commit waits for the standby. Lose the standby and writes block — that is CP behaving as advertised. Switch to local/async and you become EL with replica reads stale by the replication lag (sub-ms idle, seconds-to-minutes under a heavy write burst or a long-running query on the replica).
DynamoDBPA/EL (default)
PC/EC (per read)
Reads are eventually consistent by default and served by any of the three AZ replicas. Pass ConsistentRead=true and you route to the leader replica: 2× the read cost, a few extra ms, and unavailable if that partition has no leader. The letter is chosen per API call.
Cassandra / ScyllaDBPA/EL (default)
PC/EC (QUORUM)
Consistency level is per query. ONE is fast and stale-tolerant; QUORUM read + QUORUM write gives R + W > N and single-key linearizability-ish behaviour at the cost of latency; LOCAL_QUORUM keeps you inside one DC. Note that sloppy quorums with hinted handoff can violate the overlap guarantee during a partition — worth knowing.
Spanner / CockroachDBPC/ECPaxos/Raft per range, majority commit. Consistency is not negotiable; latency is the bill. Follower reads and bounded-staleness reads are the escape hatch when you want EL for a specific query.
ZooKeeperPC/EC (writes)Zab totally orders writes through a leader with a majority. But reads are served locally by any follower and may be stale — you must call sync() first for a linearizable read. Saying this unprompted is a strong signal.
etcdPC/ECRaft; linearizable reads by default via read-index. Minority members return errors. This is the coordination substrate for Kubernetes precisely because it refuses to guess.
MongoDBPC/EC or PA/ELw:majority + readConcern:majority is CP-ish; w:1 plus reads from secondaries is AP-ish and can lose acknowledged writes on failover (rollback files).
Redis (Sentinel or Cluster)Neither, honestlyReplication is always asynchronous, so a failover can lose writes the primary already acknowledged. It is AP-shaped without AP's conflict resolution — the losing writes just vanish. WAIT reduces the window but is not a quorum commit. Treat Redis as a cache or a lossy store, and never as your source of truth for money.
The one line to remember The choice is made per operation, not per database. Almost every modern store exposes the dial as a per-request consistency level, write concern, or read flag — so the interesting design question is never "is this system CP or AP", it is "which of my operations can tolerate a stale read, and which cannot".

The quorum dial: R + W > N

In a Dynamo-style system with N replicas, you require W acknowledgements to accept a write and R responses to serve a read. If R + W > N, the read set and the write set must overlap in at least one replica, so every read touches at least one node that saw the latest write.

writer A B C reader W = 2 (green) R = 2 N = 3 B is in both sets — the overlap is the whole guarantee
The pigeonhole principle is doing all the work: two sets of size 2 drawn from 3 replicas cannot be disjoint.
N=3 configBehaviourReach for this when…
W=1, R=1Fastest possible both ways, no overlap, reads can be arbitrarily stale. One replica loss loses data.Metrics, logs, view counters, anything where losing a write is a rounding error.
W=2, R=2Overlap guaranteed. Tolerates one node down for both reads and writes.The default. Most OLTP-ish workloads on a Dynamo-style store.
W=3, R=1Reads are single-hop fast; any node down makes writes unavailable.Read-dominated config data written rarely.
W=1, R=3Writes are single-hop fast; any node down makes reads unavailable.Rarely correct — usually a sign the workload wants W=2, R=2.
Say it like this → "I'll run N=3 across three AZs with quorum reads and writes for the orders table — cross-AZ RTT is about a millisecond so consistency is nearly free there. For the product catalogue I'll drop to R=1 and accept a few seconds of staleness, because a stale price on a listing page is recoverable and an unavailable listing page is not."

Using CAP in the room without sounding like a flashcard

The senior move is to refuse the question as posed. Nobody designs "a CP system"; you design a system in which different data has different requirements, and you say so explicitly:

  • Payments ledger, account balances, idempotency keys: CP. Refuse the write rather than double-charge.
  • Session store, presence, notification counts, feed ranking: AP. A stale unread count costs nothing.
  • Inventory: split it. The last 50 units of a SKU are sold through a CP path with a real reservation; the "1,200 in stock" badge on the listing page is AP and can be minutes old. Ticketmaster-style seat selection is the same trick — browse is AP, hold is CP.
  • Cluster metadata, leader election, feature-flag kill switches: CP, and delegated to etcd or ZooKeeper rather than built.
⚠ Three sentences that cost you the level "It's a CA system" (there is no such thing across a network). "CAP says pick two" (it says pick one, and only during a partition). "We'll use Cassandra because it's AP" (Cassandra is whatever your consistency level says it is, per query). Any of these tells the interviewer you learned CAP from a blog post rather than from an outage.

Recognizing it in an unseen problem

  • The prompt spans more than one datacentre, region, or AZ — the moment replicas can be separated by a network, CAP is live and the interviewer will probe it.
  • Words like "globally distributed", "multi-region active-active", "must never lose a write", "must always accept a write" are direct invitations to name your partition-time choice.
  • A naive design says "we'll replicate to the other region" and stops. The follow-up that exposes it is always: what does the client see while the link between them is down? Have that answer ready before it's asked.
  • Distinguish from the plain replication chapter: replication is about how copies get updated; CAP is about what you do when they can't. Distinguish from PACELC's else-branch: if the network is healthy and you are still arguing about staleness, that is a latency-vs-consistency question, not a CAP question.
  • If you choose AP, immediately state the merge function. If you choose CP, immediately state the blast radius: which clients get errors, for how long, and what the retry/queue story is so the user doesn't just see a 500.
  • The strongest closing move is to make the choice per-operation and justify each one with the business cost of being wrong versus the business cost of being down.
←previousWalkthrough: feed/chat↑ CovernextSharding at scale→