CAP Theorem in System Design: Consistency, Availability and Partition Tolerance Explained
Imagine a database cluster running across two data centers :- one in Mumbai and another in Singapore. Data is replicated between them so that either location can continue serving users if the other goes down. Now imagine the network connection between the two data centers suddenly fails. The servers in Mumbai are healthy. The servers in Singapore are healthy. Applications in both locations are still receiving requests. The only thing that has changed is that the two sides can no longer communicate with each other.
A customer in Mumbai updates their shipping address. A few seconds later, a customer service agent in Singapore requests that same account. The Singapore data center still has the previous address. Should it return that value immediately, even though it might be outdated? Or should it reject or delay the request until it can communicate with Mumbai and confirm the latest value?
There is no option that gives you everything for free once that network partition occurs. This is the core idea behind the CAP theorem.
CAP theorem is often introduced with the simple statement that a distributed system can choose only two out of three properties: Consistency, Availability and Partition Tolerance. That explanation is easy to remember, but it can also be misleading. In a real distributed system, network failures and communication problems are unavoidable, so partition tolerance is usually not something engineers can simply choose to give up.
The more useful question is: when a network partition happens, does the system prioritize returning consistent data or does it prioritize continuing to serve requests? That decision affects databases, distributed caches, payment systems, messaging platforms, shopping carts and almost every large-scale application that stores or processes data across multiple machines.
In this article, we will break down what CAP actually means, look at what happens during a network partition, understand why CP and AP systems make different trade-offs and walk through practical examples so you can recognize CAP decisions in real system design interviews and production architectures.
What Is the CAP Theorem?
The CAP theorem states that a distributed data store can only guarantee two out of the following three properties at the same time: Consistency, Availability and Partition Tolerance. It was first proposed by Eric Brewer, a computer scientist at UC Berkeley, in a keynote at the 2000 Symposium on Principles of Distributed Computing (PODC), which is why it's sometimes called Brewer's conjecture. In 2002, MIT researchers Seth Gilbert and Nancy Lynch formally proved it as a theorem in their paper "Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services."
Here's the direct answer if you just need the definition: when a network partition happens in a distributed system, you must choose between returning a consistent answer (and possibly failing the request) or returning an available answer (and possibly returning stale data). You cannot do both at the same time.
Let's define each term precisely, because loose definitions are where most confusion about CAP comes from.
Consistency (C)
In CAP theorem, consistency means that every successful read returns the most recent successful write for that data, regardless of which replica receives the request. In distributed-systems terminology, this is closely associated with linearizability or single-copy consistency.
For example, suppose a user's shipping address is stored on multiple database replicas. A request updates the address from Mumbai to Pune on one replica. If the write succeeds, a subsequent read from another replica should return Pune, not the previous value Mumbai.
This does not mean that every replica must physically store the new value at exactly the same instant. What matters is the behavior visible to the client: the system must behave as though there is a single, up-to-date copy of the data.
This is also why CAP consistency should not be confused with the C in ACID. ACID consistency is about preserving database rules and constraints when transactions execute, whereas CAP consistency is about what value a distributed system is allowed to return when the same data exists across multiple nodes.
Availability (A)
In CAP theorem, availability means that every request sent to a non-failing node receives a response, even when that node cannot communicate with other nodes in the system. Importantly, CAP availability does not mean that the response must contain the latest value. The system can return an older value, or in some designs accept a new write locally, as long as it continues responding to requests.
Consider the same shipping-address example. If the Mumbai and Singapore data centers lose connectivity, a highly available system may allow the Singapore replica to continue serving reads even though it cannot verify whether Mumbai has a newer value. That response might be stale, but the system remains available.
This is different from availability in an SLA such as 99.99% uptime. An SLA measures whether a service is operational over a period of time. CAP availability is specifically about how the system behaves when individual nodes cannot communicate because of a network partition.
Partition Tolerance (P)
A partition occurs when nodes in a distributed system can no longer reliably communicate with one another. The machines themselves may still be running perfectly, but messages between them are lost, delayed, or unable to reach their destination.
A partition can happen because of a failed network link, router or switch failure, firewall misconfiguration, packet loss, or connectivity problems between cloud regions or data centers.
Partition tolerance means the system is designed to continue operating despite these communication failures. And this is where CAP becomes particularly important.
In a real distributed system, you generally cannot assume that network communication will always work. A connection between two data centers can fail even when both data centers are completely healthy. Waiting for a perfect network is therefore not a realistic architecture.
Once a partition occurs, the system faces a fundamental choice:
- Favor Consistency: stop or reject some operations until the system can safely determine the correct state.
- Favor Availability: continue responding to requests, even if some responses may be stale or temporarily inconsistent.
That is the practical meaning of the CAP trade-off.
So, Is CAP Really "Pick 2 Out of 3"?
The popular explanation says that CAP means a distributed system can choose only two out of three: Consistency, Availability, and Partition Tolerance. The important point is that when a partition does not exist, a distributed system can often provide both consistency and availability. The difficult choice appears when a partition actually occurs.
Since network partitions are unavoidable in distributed systems, engineers generally design for Partition Tolerance and then decide what the system should sacrifice during a partition: Consistency or Availability. This is why CAP is better understood not as a simple "pick two" rule, but as a trade-off that becomes visible when communication between distributed nodes breaks down.
For example, a banking system may prefer to reject a transaction rather than risk showing or accepting incorrect account balances. A social media feed, on the other hand, may prefer to keep serving users even if some posts or counters are temporarily stale. The system is not necessarily "better" or "worse" because of that choice. The right decision depends on what the application can tolerate when the network fails.
Why You Can't Have All Three
This is the part that trips people up in interviews, so let's work through the logic directly instead of just asserting it.
Say you have two nodes, N1 and N2, both holding a copy of the same value and the network between them partitions. A client writes a new value to N1. At the same moment, another client tries to read that value from N2. N2 has two options:
- Return its old, possibly stale value. The system stayed available (it answered the request), but it violated consistency (the answer wasn't up to date).
- Refuse to answer until it can confirm with N1 (which it can't reach, because of the partition). The system stayed consistent (it never returned wrong data), but it violated availability (the request didn't get a response).
There is no third option. N2 cannot magically know N1's new value without communicating with it and it cannot communicate with it because the network is partitioned. This is the entire proof in plain language.
+------------------------------+
| Does the system have a |
| network partition? |
+---------------+---------------+
|
+---------------------+---------------------+
| |
YES NO
| |
v v
+------------------------+ +----------------------------+
| You must choose: | | You can have Consistency |
| Consistency (C) | | AND Availability at once |
| or | | ("CA") -- realistic only |
| Availability (A) | | for a single-node system |
+-----------+--------------+ +----------------------------+
|
+---------+----------+
| |
v v
+-------------+ +----------------+
| CP systems | | AP systems |
|-------------| |----------------|
| Zookeeper | | Cassandra |
| etcd | | DynamoDB |
| Spanner | | Riak, CouchDB |
+-------------+ +----------------+
Blocks/refuses on Stays available,
the minority side resolves conflicts
of the partition after the fact
Notice that "CA" (consistent and available but not partition tolerant) : only really makes sense for a single-node system or a system that assumes the network never fails. As soon as you have more than one node communicating over a real network, you have to plan for partitions, which means the actual decision in production is CP vs AP.
How It Works in Practice: The Quorum Model
Most real distributed databases don't implement a single hardcoded "CP" or "AP" behavior. Instead, they let you tune consistency using a quorum - a minimum number of nodes that must agree before a read or write is considered successful.
The standard formula, first popularized by Amazon's Dynamo paper (2007), works like this:
- N = total number of replicas holding a copy of the data
- W = number of replicas that must acknowledge a write before it's considered successful
- R = number of replicas that must respond to a read before it's returned to the client
If W + R > N, every read is guaranteed to overlap with at least one replica that has the latest write, this gives you strong consistency. If W + R ≤ N, reads can hit replicas that haven't received the latest write yet, this gives you eventual consistency, but with lower latency and higher availability, since you're not waiting on as many nodes.
N = 3 replicas | Write quorum W = 2 | Read quorum R = 2 (W + R > N = strong consistency)
CLIENT NODE A (replica) NODE B (replica) NODE C (replica)
| | | |
|--- write(k, v) ------>| | |
| |--- replicate ------->| |
| |--- replicate ------------------------------>|
| | | |
| |<-------- ACK --------| |
| | | (X) network
| | | partition:
| | | ACK lost or
| | | delayed
| | | |
|<==== write OK ========| | |
| (2 of 3 ACKs received -- quorum W=2 met, | |
| write succeeds even though Node C is | |
| unreachable) | |
This is exactly the kind of trade-off you configure directly in production systems. Here's what it looks like in Cassandra, which exposes consistency level as a per-query setting:
-- Favor availability and speed: only one replica needs to respond
SELECT * FROM orders WHERE order_id = 1042 USING CONSISTENCY ONE;
-- Favor correctness: a majority of replicas must agree
SELECT * FROM orders WHERE order_id = 1042 USING CONSISTENCY QUORUM;
And here's the equivalent idea in Amazon DynamoDB, where you choose between eventually consistent (default, cheaper, faster) and strongly consistent reads:
import boto3
table = boto3.resource('dynamodb').Table('Orders')
# Default: eventually consistent, may read from a lagging replica
response = table.get_item(Key={'order_id': '1042'})
# Strongly consistent: reads from a replica guaranteed to have the latest write
response = table.get_item(Key={'order_id': '1042'}, ConsistentRead=True)
Both examples show the same underlying idea from the CAP theorem, just exposed as an application-level knob instead of a fixed architectural choice.
Real-Life Example: The Shopping Cart Problem
Amazon's engineering team documented this trade-off directly in their 2007 Dynamo paper, which described the design behind Amazon's internal key-value store (the ancestor of DynamoDB). Their reasoning is a genuinely useful mental model for CAP in practice.
For a shopping cart, losing availability is worse than briefly showing stale data. If a customer adds a phone case to their cart and the system refuses to respond because it can't confirm consistency across replicas, that customer sees an error and may just leave. But if the system returns a slightly outdated version of the cart, maybe missing the last item they added. it can quietly merge the conflicting versions later and the customer barely notices. Amazon's system chose availability (AP) for cart writes, using a technique called vector clocks to detect and merge conflicting versions after the fact, rather than blocking the write until consistency was guaranteed.
Contrast that with a banking ledger. If a customer transfers money out of one account, the system genuinely cannot afford to show two tellers two different balances that both look "current." Here, refusing a request (unavailable) is safer than the risk of a double-spend or an incorrect statement (inconsistent). This is why most core banking systems and payment ledgers lean CP, even at the cost of occasional downtime during a partition.
Neither choice is "correct" in the abstract. They're correct for the problem they're solving.
Real-World Engineering Incident: GitHub, October 2018
This isn't a hypothetical. it's one of the more thoroughly documented public incidents involving exactly this trade-off and GitHub published a detailed post-incident analysis on their engineering blog.
On October 21, 2018, routine maintenance on optical networking equipment caused a 43-second loss of connectivity between GitHub's US East Coast network hub and their primary data center. GitHub runs MySQL clusters with replicas spread across both coasts, managed by an orchestration tool (Orchestrator) that uses the Raft consensus algorithm to handle automatic failover.
During those 43 seconds, Orchestrator detected that the East Coast primary was unreachable and did what it was designed to do. it promoted a West Coast replica to primary, so the cluster could keep accepting writes. That's the system choosing availability during a partition. The problem was that some writes had already landed on the East Coast primary but hadn't replicated to the West Coast before the link dropped and after failover, new writes started landing on the West Coast copy too. When the network came back, GitHub had two divergent sets of data, writes on the East Coast that the West Coast didn't have and vice versa and no safe way to merge them automatically without risking data loss or corruption.
GitHub's team made a deliberate choice at that point: prioritize data integrity over restoring service quickly. As they put it in their own write-up, they chose to "prioritize data integrity over site usability and time to recovery." Restoring from backups and letting replication fully catch up before failing back safely took 24 hours and 11 minutes of degraded service, even though the network partition itself lasted less than a minute. This is a textbook illustration of two things CAP theorem discussions often gloss over: partitions can be extremely short and still trigger consequences that last far longer and a CP-leaning decision can mean choosing a long, visible outage over a fast but unsafe recovery.
Sources referenced:
PACELC: The Theorem CAP Doesn't Cover
CAP only describes what happens during a network partition. But most of the time, your system isn't partitioned the network is fine and you're just deciding how to balance latency against consistency for everyday reads and writes. Daniel Abadi, a database researcher, pointed this gap out in a 2012 paper and proposed an extension called PACELC.
It reads as: if there is a Partition (P), you choose between Availability (A) and Consistency (C) Else (E), during normal operation, you choose between Latency (L) and Consistency (C).
This matters because a system's behavior during a partition (rare) and its behavior during normal operation (constant and directly affects every user's experience) are two separate design decisions. Cassandra, for instance, is PA/EL available over consistent during a partition and low-latency over consistent during normal operation. Google Spanner, using tightly synchronized clocks (TrueTime) and consensus across replicas, is PC/EC it pays a latency cost even in normal operation to guarantee strong consistency.
| System | During Partition | During Normal Operation |
|---|---|---|
| Cassandra (default settings) | PA stays available | EL favors low latency |
| DynamoDB (eventually consistent reads) | PA | EL |
| Google Spanner | PC blocks for consistency | EC favors consistency over raw latency |
| Zookeeper / etcd | PC | EC |
| MongoDB (default read from primary) | PC-leaning elects new primary, brief unavailability | EC-ish, tunable per read/write concern |
CP vs AP: How Real Systems Choose
| Category | Examples | Behavior During a Partition | Typical Use Case |
|---|---|---|---|
| CP (Consistent, Partition-tolerant) | Zookeeper, etcd, Google Spanner, HBase | Minority-side nodes stop serving writes (and often reads) until the partition heals or a new majority is established | Configuration stores, leader election, distributed locks, financial ledgers |
| AP (Available, Partition-tolerant) | Cassandra, DynamoDB, Riak, CouchDB | All reachable nodes keep serving requests, possibly returning stale data, reconciled later | Shopping carts, social media feeds, product catalogs, session stores |
| CA (theoretical only) | Single-node databases or systems that assume the network never partitions | Not meaningfully achievable for multi-node systems over a real network | N/A in practice |
A concrete example of CP in action: etcd is the backing store for Kubernetes cluster state. It uses the Raft consensus algorithm, which requires a majority (quorum) of nodes to agree before committing a write. If a network partition splits a 5-node etcd cluster into a group of 3 and a group of 2, only the group of 3 (the majority) can keep accepting writes the minority group of 2 becomes read-only or fully unavailable for writes. This is intentional: Kubernetes would rather pause cluster state changes than risk two halves of the cluster disagreeing about what pods should be running where.
BEFORE PARTITION -- 5-node etcd cluster, all connected, one leader
[N1]---[N2]---[N3]---[N4]---[N5] N3 = Leader (elected via Raft)
\ \ | / /
\______\_____|_____/______/ All 5 nodes accept reads;
| writes go through the leader
LEADER (N3)
--------------------------------------------------------------------
AFTER PARTITION -- network splits the cluster into two groups
Majority side (3 nodes) | Minority side (2 nodes)
+-------------------------+ | +-------------------------+
| [N1]--[N2]--[N3] | X | [N4]--[N5] |
| has 3 of 5 votes | no | has 2 of 5 votes |
| = quorum (>50%) | traffic | = NOT a quorum |
| | crosses | |
| Elects/keeps a leader | the | Cannot elect a leader |
| Keeps ACCEPTING writes | split | Rejects/blocks writes |
+-------------------------+ | +-------------------------+
|
Result: cluster stays CORRECT (CP) by letting only the
majority side make progress -- the minority side goes
read-only rather than risk a split-brain write conflict.
Technical Deep Dive: Consensus Algorithms and Conflict Resolution
CP systems generally rely on consensus algorithms protocols that let a group of nodes agree on a single value even if some nodes fail or messages are delayed. The two most widely used are:
- Paxos the original practical consensus protocol, used inside systems like Google Chubby and Spanner. It's correct but notoriously difficult to implement and reason about.
- Raft designed later specifically to be easier to understand and implement than Paxos, while providing the same guarantees. It's used by etcd, Consul and CockroachDB and by GitHub's own Orchestrator (as seen in the incident above).
Both work on the same basic principle: a write is only considered committed once a majority of nodes acknowledge it, so any future leader election or read is guaranteed to see that write, because any majority overlaps with any other majority by at least one node.
AP systems, on the other hand, need a way to detect and resolve conflicting versions of the same data after the fact, since they allow writes to succeed on both sides of a partition. Common approaches:
- Last-write-wins (LWW): compare timestamps, keep the newer one. Simple, but can silently lose data if clocks are skewed.
- Vector clocks: track causality between versions (used in the original Dynamo design) so the system can tell "these two versions are genuinely conflicting" apart from "this version is simply older."
- CRDTs (Conflict-free Replicated Data Types): data structures specifically designed so that concurrent updates can always be merged deterministically without needing a central coordinator used in systems like Redis (in some configurations) and collaborative editing tools.
Trade-offs, Limitations and Common Mistakes
-
"CAP means I can only pick two properties, ever." Not quite CAP is specifically about behavior during a network partition. Outside of a partition, well-designed systems try to deliver consistency, availability and reasonable latency simultaneously. The forced trade-off only kicks in when nodes can't communicate.
-
"Partition tolerance is optional, so I could build a CA system." In theory only. Any system with more than one node communicating over a real network including within a single data center, across racks or between availability zones in the same cloud region is exposed to partitions. Treating P as optional is really just choosing not to plan for a failure mode that will eventually occur.
-
"Eventual consistency means the data is wrong." Eventual consistency means the data will converge to the correct, most recent value given enough time and no new writes it's a real, well-defined consistency model, not an excuse for bugs. The practical question is whether your application can tolerate the convergence window (usually milliseconds to a few seconds in modern systems, but can be longer under heavy load or during recovery).
-
"CAP availability and SLA availability are the same thing." They're not. A system can have excellent SLA-measured uptime (say, 99.99% over a year) while still being "CP" in the CAP sense because CP-driven downtime during rare partitions is usually a tiny fraction of total time. Confusing the two leads to arguing past each other in design reviews.
Common practical mistakes:
- Choosing a CP database for a use case that actually needs high write availability (like activity logs or metrics ingestion) and then being surprised by request failures during minor network blips.
- Choosing an AP database for a use case that needs strict correctness (like unique username reservation or inventory counts for a limited flash sale) and then dealing with double-bookings or oversold inventory.
- Assuming a database is "CP" or "AP" as a blanket label without checking per-operation settings many modern databases, including Cassandra and MongoDB, let you tune consistency per query or per collection, so the real answer is often "it depends how you configured that specific read or write."
When to Use CP vs AP
Use a CP-leaning approach when incorrect data is worse than no data financial transactions, inventory for scarce or non-fungible items, distributed locks, leader election, configuration data that controls system behavior. A short, visible failure is preferable to silent corruption.
Use an AP-leaning approach when staying responsive matters more than having the absolute latest value product catalogs, user profile caches, activity feeds, shopping carts, analytics counters, session data. A slightly stale response is preferable to an error page.
Don't treat this as a database-wide decision if your workload has mixed needs. It's common and often the right call to run your order totals through a CP-consistent path while your product recommendation feed runs through an AP path, even within the same application.
Performance, Scalability and Observability Considerations
Performance and latency
CP systems that require quorum agreement on every write pay a latency cost proportional to network round-trip time between replicas, especially across regions. AP systems that accept a write locally and replicate asynchronously return faster, at the cost of a replication lag window.
Scalability
AP systems generally scale write throughput more easily, since any reachable replica can accept a write without waiting on a quorum across potentially distant nodes. CP systems are often bottlenecked by the need for a majority of nodes (frequently an odd number, like 3 or 5, to avoid ties) to coordinate, which limits how far you can spread replicas geographically without hurting write latency.
Reliability and disaster recovery
CP systems fail closed during a partition they stop rather than risk wrong answers, which is safer for correctness but means partitions translate directly into visible downtime for affected operations. AP systems fail open, staying available but pushing the burden of eventual reconciliation onto the application or the database's conflict-resolution logic.
Observability
Whichever model you pick, you need visibility into replication lag (for AP systems) or quorum health and leader election events (for CP systems). A CP cluster that's silently down to a minority partition or an AP cluster with growing replication lag, will look "fine" on a basic uptime check while actually degrading dashboards should track these specifically, not just request success rates.
Cost
CP systems with synchronized clocks and cross-region consensus (like Spanner) generally cost more to run at scale because they depend on specialized infrastructure (Spanner's TrueTime relies on GPS and atomic clocks in Google's data centers) or because cross-region quorum writes consume more network bandwidth and compute for coordination. AP systems can often run more cheaply on commodity infrastructure since nodes work more independently.
Security
CAP itself doesn't directly address security, but the trade-off has security implications AP systems that resolve conflicts automatically need care to make sure conflict resolution logic can't be abused (for example, replaying an old write to "win" a last-write-wins race). CP systems that fail closed during a partition are naturally more resistant to an attacker exploiting a network disruption to force inconsistent state, since they simply refuse to serve requests in that scenario instead.
Interview Perspective
CAP theorem comes up often in system design interviews, usually as a way to test whether a candidate understands trade-offs rather than memorized definitions. A few questions worth being ready for:
-
"Design a system and tell me if it's CP or AP and why." The interviewer wants you to justify the choice based on the specific requirement (e.g., "this is a rate limiter, so I'd lean AP because a slightly stale count is fine and blocking requests during a partition would hurt user experience more than a small over-count would").
-
"Can you have a CA system?" The strong answer isn't just "no" it's explaining that CA is only meaningful without a network partition, so it applies to single-node systems and any real distributed system has to plan for P.
-
"How would you handle a network partition in [specific system]?" This tests whether you can apply quorum concepts, replication strategy and conflict resolution rather than just naming CAP.
-
"What's the difference between CAP's availability and the availability in an SLA?" A good answer distinguishes per-request availability during a partition versus aggregate uptime over time many candidates conflate these.
-
"Explain PACELC and why it matters beyond CAP." This signals deeper knowledge that you understand partitions are rare, but the latency-vs-consistency trade-off during normal operation affects every single request.
Frequently Asked Questions
Is CAP theorem still relevant today?
Yes. It's not a legacy academic idea it directly explains why databases like Cassandra, DynamoDB, MongoDB and etcd expose tunable consistency settings and it's the reasoning behind real production incidents like GitHub's 2018 outage.
Can a system be both CP and AP?
Not for the same operation during an actual partition that's what the theorem rules out. But a single system can offer both behaviors for different operations or different consistency-level settings, which is exactly what Cassandra's per-query consistency levels and DynamoDB's consistent-read flag let you do.
Is NoSQL always AP and SQL always CP?
No, that's an oversimplification. Traditional relational databases with synchronous replication behave in a CP-leaning way, but plenty of NoSQL systems (MongoDB with majority write/read concerns or any system using Raft/Paxos) are CP. And some SQL-compatible distributed databases like CockroachDB are explicitly CP via consensus.
What's the difference between eventual consistency and strong consistency?
Strong consistency guarantees any read reflects the latest committed write. Eventual consistency guarantees that if no new writes occur, all replicas will converge to the same value eventually but a read shortly after a write might return an older value.
Does CAP theorem apply to a single-node database?
Not meaningfully. CAP is about trade-offs across multiple nodes communicating over a network. A single-node database doesn't have a partition problem in the same sense, though it has its own single-point-of-failure risk.
How is CAP different from ACID?
ACID (Atomicity, Consistency, Isolation, Durability) describes guarantees for a single transaction within one database. CAP describes trade-offs across multiple replicas or nodes in a distributed system. They operate at different layers and aren't direct substitutes for each other a system can be ACID-compliant on a single node while still facing CAP trade-offs once it's replicated.
The Bottom Line
CAP theorem isn't a rule that tells you which database to pick it's a lens for understanding why every distributed database you'll ever use has made a deliberate trade-off, usually one you can tune per operation rather than accept as fixed. The real skill isn't reciting "pick two of three." It's being able to look at a specific read or write in your system a bank transfer, a cart update, a leader election and reason about whether returning slightly stale data or briefly refusing to answer is the lesser evil for that particular case. Get that judgment right and the rest of your distributed systems design tends to follow naturally.
