Skip to content

Cloud & Infrastructure · Architecture Patterns

CAP Theorem in Practice: What It Actually Constrains and What It Doesn't

CAP theorem gets summarized as 'pick two of three' so often that the summary has replaced the theorem. Here's what it actually says, why the real constraint only bites during a network partition, and how to pick a consistency model for a system you're actually building.

Prathviraj Singh

Prathviraj Singh

6 min read

CAP Theorem in Practice: What It Actually Constrains and What It Doesn't

Sponsored

Share

A three-node database cluster loses network connectivity between two of its nodes for four minutes. Nothing crashed, nothing lost power, packets from one node just stopped reaching the other two. In those four minutes, every write to that isolated node either has to be rejected, or accepted and reconciled later, and there is no third option that gives you both correctness and uptime at once. That four-minute window is what CAP theorem is actually about. Everything before and after it, the system’s normal operation, is a different question the theorem doesn’t answer.

What CAP actually says

CAP theorem, formalized by Eric Brewer and later proven formally by Seth Gilbert and Nancy Lynch, concerns three properties of a distributed data store:

  • Consistency: every read receives the most recent write or an error. Every node in the system agrees on the current state.
  • Availability: every request receives a response, success or failure, without guaranteeing it contains the most recent write.
  • Partition tolerance: the system continues operating despite network partitions, meaning some nodes can’t communicate with others.

The theorem says: when a partition occurs, you must choose between consistency and availability. You cannot have both during that partition. That’s the entire claim. It is not “pick any two of the three, always,” which is how it gets taught and repeated, and that phrasing causes real confusion, because it implies partition tolerance is one of three equally optional properties. It isn’t. Any system that runs on more than one node, over a real network, is subject to partitions whether it wants to be or not. Network partitions aren’t a design choice you opt into. They’re a fact about distributed systems that you have to have an answer for.

So the actual decision every distributed data store makes isn’t “C, A, or P, pick two.” It’s “when a partition happens, and it will, do we sacrifice consistency or availability to keep operating.” That’s a narrower, more useful question, and it’s the one worth asking about any system you’re evaluating or building.

During normal operation, a distributed system can be both consistent and available. The moment a network partition splits the cluster, it has to choose: reject requests on the minority side to stay consistent (CP), or keep serving requests on both sides and reconcile the conflict later (AP)

CP systems: correctness over uptime

A CP system responds to a partition by refusing requests on the side that can’t confirm it has the latest state, rather than risk returning or accepting stale data. ZooKeeper and etcd are the canonical examples, both built specifically for coordination tasks, leader election, distributed locks, configuration state, where an incorrect answer is worse than no answer. If a etcd cluster loses quorum during a partition, it stops accepting writes entirely on the minority side rather than let two sides of a split cluster both believe they’re authoritative.

Traditional relational databases with synchronous replication behave the same way for the same reason: if the primary can’t confirm the replica received a write, it doesn’t acknowledge the write as successful, even though that means the client waits, or fails, during the partition. This is the same logic behind distributed locks: when correctness genuinely can’t bend, you accept the availability cost that comes with refusing to guess.

AP systems: uptime over immediate correctness

An AP system takes the opposite position: keep serving requests on both sides of a partition, and resolve any conflicts once the partition heals. Cassandra, DynamoDB, and Riak are the standard examples, and they’re built for workloads where “the system is down” is a worse outcome than “a read is a few seconds stale.”

Cassandra makes this an explicit, tunable knob rather than a fixed architectural choice, via consistency levels set per query:

-- ONE: fastest, least consistent. Any single replica answering is enough.
SELECT * FROM orders WHERE order_id = ? USING CONSISTENCY ONE;

-- QUORUM: a majority of replicas must agree. Slower, stronger guarantee.
SELECT * FROM orders WHERE order_id = ? USING CONSISTENCY QUORUM;

That’s the practical shape CAP takes in a real system: not a single global decision made once at architecture time, but a per-query or per-table dial between “answer fast, might be stale” and “answer correctly, might be slow or unavailable.” A shopping cart read can reasonably run at ONE, a small chance of showing a slightly stale cart is an annoyance. An inventory decrement that prevents overselling the last unit of something needs QUORUM or stronger, because the cost of being wrong is asymmetric.

The part CAP doesn’t cover: PACELC

CAP only constrains behavior during a partition, but partitions are, for most systems most of the time, rare. The more relevant everyday question is what a system does when the network is healthy and there’s no partition to force a choice at all. That’s what PACELC, an extension proposed by Daniel Abadi, actually addresses: if Partitioned, choose Availability or Consistency; Else, choose Latency or Consistency.

Even with no partition in sight, a system replicating data across multiple nodes or regions still has to decide whether a write waits for confirmation from every replica (higher consistency, higher latency) or returns as soon as one node has it, with replication happening in the background (lower latency, a brief window where a read from a different replica could miss the write). This is the tradeoff most teams actually live with day to day, since a genuine network partition is a rare event, but the latency-versus-consistency dial on every write gets turned constantly, often without anyone explicitly deciding where it should sit.

SystemPartition behaviorEveryday tradeoff (no partition)
etcd, ZooKeeperCP: rejects writes on minority sideEvery write waits for quorum, higher latency, strong consistency
PostgreSQL (sync replication)CP: primary blocks until replica confirmsWrite latency scales with the slowest synchronous replica
Cassandra, DynamoDBAP: keeps serving both sidesTunable per query, from fast/eventual to slow/strongly consistent
MongoDB (default)Leans CP with a single primaryReads from secondaries can lag; configurable read/write concern

Choosing a consistency model for something you’re actually building

Skip the abstract “which letter do I want” framing and ask what a stale or unavailable answer actually costs for the specific data involved. A product recommendation feed being a few seconds out of date costs nothing. A payment ledger being a few seconds out of date, or worse, momentarily inconsistent across replicas, costs real money and trust. Most real systems aren’t purely CP or purely AP end to end, they make this decision per subsystem: strongly consistent for the ledger, eventually consistent for the activity feed, same way a system might use different load balancing algorithms for different traffic shapes rather than picking one algorithm for everything.

The mistake worth avoiding is treating CAP as a one-time architectural decree made at the start of a project and never revisited. It’s closer to a dial you set differently for each piece of data based on what wrong actually costs there, checked again whenever a subsystem’s actual failure cost changes.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored