The CAP Theorem, Explained with Real Examples
Every distributed database vendor claims to be fast, consistent, and always available. The CAP theorem is the reason you should be suspicious of that pitch: in the presence of a network partition, you can only keep two of Consistency, Availability, and Partition tolerance, and partition tolerance isn't actually optional — so the real choice is between consistency and availability.
What the three letters actually mean
- Consistency (C): every read receives the most recent write, or an error. All nodes see the same data at the same time.
- Availability (A): every request receives a (non-error) response, without guaranteeing it contains the most recent write.
- Partition tolerance (P): the system keeps operating despite an arbitrary number of messages being dropped or delayed between nodes.
Network partitions happen — a switch fails, a data-center link saturates, a node's NIC flakes out. Since you can't opt out of the network being unreliable, P isn't a real design choice; it's a fact of distributed systems. That leaves you picking between C and A during a partition. Outside of a partition, a well-built system can give you both.
Why "pick two" is misleading
The classic "pick two of three" framing suggests CA is a valid option, as if partition tolerance were something you could turn off. It isn't, unless your system runs on a single machine with no network calls at all. So in practice the theorem reduces to one question: when a partition happens, does your system stay available at the cost of consistency, or stay consistent at the cost of availability?
CP in practice: a leader-based configuration store
Consider a configuration service backed by a Raft-based store (etcd, Consul, ZooKeeper). Writes go through a leader, which must replicate to a majority of nodes before acknowledging.
Client -> Leader -> [replicate to majority] -> ack -> Client
If a network partition splits the cluster into two groups, only the group containing a majority of nodes can elect a leader and accept writes. The minority side stops accepting writes entirely — it returns errors rather than risk serving stale or conflicting data.
# Simplified: a node in the minority partition rejects writes
def handle_write(request):
if not self.has_quorum():
raise Unavailable("no quorum: refusing to risk inconsistent write")
return self.replicate_and_commit(request)
That's CP: consistency is preserved by sacrificing availability on the minority side. This is exactly what you want for configuration data, leader election, or distributed locks — serving a stale lock owner is worse than a temporary outage.
AP in practice: a shopping cart service
Now consider a shopping cart stored in a Dynamo-style database (Cassandra, DynamoDB) with multiple replicas across regions. During a partition, every replica keeps accepting writes independently.
# Simplified: every reachable replica accepts writes during a partition
def handle_write(request):
self.local_store.put(request.key, request.value, timestamp=now())
return Ack() # never blocks on other replicas
Two regions might now have diverging versions of the same cart. When the partition heals, the system needs a reconciliation strategy — last-write-wins by timestamp, vector clocks, or an application-level merge (union the items instead of picking one side).
# A simple merge strategy: union cart items instead of picking a winner
def merge_carts(cart_a, cart_b):
return {**cart_a, **cart_b} # naive union; real systems need care around removals
That's AP: the cart service stays available on both sides of the partition, and correctness is repaired after the fact rather than enforced during the write. For a shopping cart, an available-but-eventually-consistent system beats a consistent-but-down one — nobody wants "service unavailable" between browsing and checkout.
It's not a single system-wide choice
The useful realization is that CAP applies per operation, not to a database product as a whole. Many real systems mix both: DynamoDB offers both eventually-consistent and strongly-consistent reads on the same table. PostgreSQL with synchronous replication is CP for the primary's writes but can be configured with asynchronous standbys that serve stale (AP-leaning) reads.
-- PostgreSQL: synchronous_commit controls where you sit between C and A
SET synchronous_commit = 'on'; -- wait for replica ack: favors consistency
SET synchronous_commit = 'off'; -- don't wait: favors availability/latency
Design at the level of individual operations: does this write need linearizable consistency, or can this read tolerate being a few seconds stale? Answering per-operation, instead of per-database, is where CAP actually earns its keep as a design tool.
PACELC: the theorem's underrated sequel
CAP only describes behavior during a partition. Daniel Abadi's PACELC extension adds the more common case: even without a partition, you trade latency for consistency. If Partitioned, choose Availability or Consistency; Else (normal operation), choose Latency or Consistency.
A system that replicates synchronously to a remote region gets strong consistency but pays a cross-region round-trip on every write. A system that replicates asynchronously gets low latency but readers might see stale data even when the network is perfectly healthy. This trade-off is present on every single request, not just during rare partition events — which is why it matters more for day-to-day system design than the partition case does.
Applying this when you actually design something
- Identify which operations are locks/leader-election/inventory-decrement style (need CP) versus which are cart/profile/analytics style (can tolerate AP).
- Don't pick a database because of a marketing claim of "always consistent and always available" — that combination doesn't survive a real partition. Ask specifically what it does when nodes can't reach each other.
- Check whether the consistency knob is per-cluster or per-request. Systems like DynamoDB and Cassandra let you choose per read, which is usually the right level of granularity.
- Remember PACELC: your consistency choice has a latency cost on the happy path, not just a risk cost during outages.
The CAP theorem isn't a reason to be anxious about distributed systems — it's a checklist question to ask of any database before you build on it: what exactly happens to my requests when a partition hits, and which side of that trade-off does my data actually need?