System Design
The CAP Theorem Trade-off I Made in Production
Most candidates can recite 'consistency, availability, partition tolerance - pick two.' Then I ask one follow-up and watch the answer fall apart.
I've run something like three hundred interview loops. There's a moment in the system design round I can almost set my watch by: I ask about a system that spans regions, the candidate reaches for the CAP theorem, and they say the line. "Consistency, availability, partition tolerance - you can only pick two." Confident. Rehearsed.
Then I ask: "Okay. Which two are you giving up, and when?"
And most of the time, that's where it stops. Because "pick two" is what you memorize when you've read about CAP but never had to live inside the decision. I've had to live inside it - my day job is keeping a storage system consistent across regions when the network between those regions is, frankly, not always cooperative. So let me give you the version of CAP that survives the follow-up question, built from the ground up.
What CAP actually says
Three properties. A distributed system would love to have all three:
- Consistency (C) - every read returns the most recent write. Everyone sees the same data at the same time. (Note: this is not the C in ACID. Same word, different meaning. The overloaded vocabulary is half of why this topic confuses people.)
- Availability (A) - every request gets a non-error response. The system always answers, even if the answer might be slightly out of date.
- Partition tolerance (P) - the system keeps working even when the network drops messages between nodes.
The CAP theorem, proven formally by Gilbert and Lynch, says you can't have all three at once. The popular framing is "pick two."
Here's why that framing is a trap.
The trap: "pick two" is the wrong mental model
Partitions are not something you choose. A partition is what happens when a network cable gets cut, a switch fails, or a region briefly can't talk to another region. In any system that runs across more than one machine - which is every system worth designing for an interview - partitions are a fact of nature, not a menu option.
So you don't get to "give up P." If you give up partition tolerance, what you actually have is a single-node system that falls over the moment the network hiccups. That's not a design; that's a liability.
Important: In a real distributed system, P is mandatory. The CAP theorem isn't "pick two of three." It's: when a partition happens, you must choose between C and A. That's it. That's the whole theorem, stated honestly.
This reframing is the entire difference between a candidate who memorized CAP and one who understands it. When the network splits and two halves of your system can't talk:
- Choose Consistency → refuse to answer (or refuse writes) on the side that might be stale, so nobody reads wrong data. You stay correct but you become unavailable for some requests.
- Choose Availability → keep answering on both sides, accept that they might diverge, and reconcile later. You stay up but you risk serving stale or conflicting data.
That's the trade I make in production. When two regions can't reach each other, do I reject the write and tell the user "try again," or do I accept it locally and merge later? There is no free answer. There's only the answer that's right for this data.
graph TD
P{Network partition<br/>happens between nodes} --> Q{Can I still guarantee<br/>every read is current?}
Q -->|Refuse stale answers| C[CP: stay Consistent<br/>sacrifice Availability]
Q -->|Keep answering anyway| A[AP: stay Available<br/>sacrifice Consistency]
C --> C2[e.g. payments, inventory,<br/>anything where wrong = costly]
A --> A2[e.g. likes, feeds, presence,<br/>anything where stale = fine]
This flowchart shows the real CAP decision. It starts when a network partition happens between nodes. The next question asks whether the system can still guarantee every read is current. If it refuses to give stale answers, it becomes a CP system: consistent but not always available - used for payments and inventory. If it keeps answering anyway, it becomes an AP system: available but possibly inconsistent - used for likes, feeds, and presence.
It's not one global choice - it's per-operation
Here's the second thing that separates levels. CAP is not a single switch you flip for your whole system. A serious architecture makes the C-vs-A call per type of data.
Take a shopping platform during a network partition:
- The "add to cart" action can lean Available. If two replicas briefly disagree about your cart, you merge the items later. Worst case, a user sees an extra item for a few seconds. Nobody gets hurt.
- The "charge the card" action must lean Consistent. I am not going to accept a payment on a replica that can't confirm it isn't double-charging someone. I'd rather return an error and have the user retry than corrupt money.
Same system. Same partition. Two different CAP choices, because the cost of being wrong is different for each. When a candidate tells me "this whole system is AP," I know they're reciting. When they say "the cart is AP, the payment path is CP, and here's why," I know they've actually thought about it.
The mechanism: quorums
How do you actually tune where a system sits on the C-vs-A line? The most common lever is the quorum.
Say your data is replicated across N nodes. You decide:
- W = how many nodes must acknowledge a write before you call it successful.
- R = how many nodes you read from and compare.
The magic condition is W + R > N. When that holds, the set of nodes you wrote to and the set you read from are guaranteed to overlap by at least one node - so every read is guaranteed to see the latest write. Prove it to yourself:
def strong_reads(N, W, R):
# W + R > N guarantees the write-set and read-set overlap
return W + R > N
configs = [(3, 1, 1), (3, 2, 2), (3, 3, 1), (5, 3, 3), (3, 1, 3)]
print(f"{'N':>2} {'W':>2} {'R':>2} guarantee")
print("-" * 40)
for N, W, R in configs:
g = "strong (reads always current)" if strong_reads(N, W, R) else "eventual (reads may be stale)"
print(f"{N:>2} {W:>2} {R:>2} W+R={W+R:<2} -> {g}")
Run it. N=3, W=1, R=1 gives you fast, highly-available reads and writes - but W+R=2, which is not greater than 3, so you can read stale data. N=3, W=2, R=2 gives W+R=4 > 3 - now reads are always current, but you need two nodes reachable for every operation, so a partition is more likely to make you unavailable. The quorum numbers are your CAP dial. Turn them toward consistency and you sacrifice availability under partition, and vice versa.
Interview: When I'm running the loop, this is the depth that earns a "strong hire." Don't just name CAP - reach for quorums and say something like: "I'll replicate across 3 nodes. For the payment path I'll use W=2, R=2 so reads are always consistent. For the activity feed I'll use W=1, R=1 so it stays fast and available, and accept eventual consistency." That sentence does more for you than ten minutes of definitions.
PACELC: the part nobody mentions
CAP only talks about what happens during a partition. But partitions are rare. What about the 99.9% of the time the network is fine?
That's what the PACELC extension adds: if Partition, then Availability-or-Consistency (PAC); Else, Latency-or-Consistency (ELC). Translation: even when nothing is broken, you still trade consistency against latency. Wanting every read to be perfectly current means coordinating across nodes, and coordination costs milliseconds. Sometimes a lot of them.
This is the honest, complete picture. CAP is the dramatic version that only shows up during failures. PACELC is the everyday version you're actually paying for on every single request.
Where real systems land
| System | Partition behavior | Why |
|---|---|---|
| Google Spanner | CP (leans consistent) | Built for strong global consistency; will sacrifice availability to never serve stale data |
| Cassandra / DynamoDB | AP (tunable) | Built to always answer; consistency is tunable per query via quorum settings |
| A single SQL primary | CP | Strong consistency; if the primary is partitioned away, that data is unavailable |
| DNS | AP | Always answers, propagates updates eventually - staleness is acceptable |
Notice there's no "best" row. Spanner and Cassandra were built by brilliant teams who made opposite CAP choices, because they were solving different problems. That's the real lesson: CAP isn't a problem you solve. It's a trade-off you make on purpose, with your eyes open, per piece of data.
The candidates who pass my loop aren't the ones who recite the theorem fastest. They're the ones who, when I cut the network in the hypothetical, calmly tell me which requests they'll fail and which they'll keep serving stale - and why that's the right call for this data. That judgment is learnable. It's most of what System Design Fundamentals is about, and my co-founder Nikhil takes the consistency side of this even deeper in his piece on strong vs eventual consistency. Start there if this clicked.
Go deeper
- The CAP theorem - the formal statement and the Gilbert–Lynch proof it links to.
- The PACELC theorem - the "else, latency vs consistency" extension that completes the picture.
- Amazon Builders' Library - how Amazon actually reasons about availability and consistency trade-offs in production.
- Google Cloud Spanner - a real CP system that chose strong consistency at global scale, and what that costs.