Skip to content

CAP Theorem

Prerequisites: Replication basics, Network fundamentals

← Distributed Systems Overview | Next: Consistency Models →


Why This Exists

In 2000, Eric Brewer observed that distributed systems face an unavoidable tension. You cannot simultaneously guarantee all three of:

  • Consistency — a single-copy / linearizable view: every operation appears to occur on one up-to-date copy of the data, respecting real-time ordering. (This is a much stronger guarantee than "eventually correct" — see Consistency Models for the weaker variants: causal, read-your-writes, sequential.)
  • Availability — every request sent to a non-failing node eventually receives a response, even though that response may not reflect the latest write
  • Partition Tolerance — the system keeps operating even when network messages are lost/delayed between nodes

The CAP theorem formalizes this: in the presence of a network partition, you must choose between consistency and availability.

Mental Model

Imagine two database nodes (Node A and Node B) connected by a network cable. You pull out the cable — a network partition occurs.

  • Node A gets a write request. Should it accept it and risk diverging from Node B?
  • If it accepts → Available but inconsistent
  • If it rejects → Consistent but unavailable
  • Partition Tolerance is not a choice — networks fail. The real choice is C vs A during a partition.

Abstraction Levels

Pick two of Consistency, Availability, Partition Tolerance — and since networks fail, Partition Tolerance isn't optional, so it's really C vs. A during a partition.

Typical interview shorthand: "MongoDB is CP, Cassandra is AP." Fine as a fast first answer for common configurations. Be ready to go one level deeper if asked "why," because the honest answer is per-configuration — topology, election, and read/write concern — not a permanent product identity.

CP/AP is a property of a specific operation under a specific configuration, not a fixed database identity — see How Real Databases Behave. Replication mode (sync/async) controls durability and latency; partition behavior is actually decided by quorum/consensus and fencing policy. Two systems both labeled "CP" can still differ in exactly which reads are guaranteed linearizable.

Real outages are rarely a clean, total partition — they're partial, asymmetric (A can reach B but not vice versa), or a slow/flaky link rather than a severed one. CAP's binary framing is a useful teaching model; production incident response usually looks more like Consistency Models's spectrum of guarantees than a single C-vs-A toggle.


The Unavoidable Choice

graph TD
    P["Network Partition\n(cable pulled)"] --> Q{What does Node A do?}
    Q -->|Accept write| A["Available ✓\nConsistent ✗\n(nodes diverge)"]
    Q -->|Reject write| C["Consistent ✓\nAvailable ✗\n(returns error)"]
    style P fill:#b71c1c,color:#fff
    style A fill:#f57f17,color:#fff
    style C fill:#1565c0,color:#fff

CAP Categories

Typical interview shorthand — not a permanent product identity. The same database can look CP-like or AP-like depending on topology and consistency settings.

Category Guarantee Systems commonly configured this way Depends on Use When
CP Consistent + Partition Tolerant ZooKeeper, etcd, HBase (typically CP); MongoDB (often CP-like) Consensus/quorum (etcd/ZK writes are not optional); Mongo topology and read/write concern Financial data, config, coordination
AP Available + Partition Tolerant Cassandra, CouchDB, DynamoDB (eventual mode) Replication factor + chosen consistency level Shopping carts, social feeds, DNS
CA Consistent + Available Single-node RDBMS Not a distributed system — partitions aren't tolerated Single-node only

CP/AP here describes the behavior of a specific operation under a specific configuration during a partition — not a database's permanent identity. Cassandra, DynamoDB, and MongoDB are tunable per the table in How Real Databases Behave. etcd/ZooKeeper writes are not optional CP knobs.

Production Trap

"CA" systems don't truly exist in distributed systems. Any distributed system must tolerate network partitions — otherwise a partition causes complete system failure. CA means "single node" in practice.


How Real Databases Behave

"CP" and "AP" are configuration defaults, not fixed identities — most of these systems let you dial the trade-off per-query. What matters in an interview is knowing the default, why it was chosen, and — critically — that what actually determines partition behavior is topology, quorum/fencing policy, and which reads/writes are allowed during a partition, not replication mode alone. Sync vs. async replication changes acknowledgement durability and latency; it doesn't by itself decide what happens when nodes can't reach each other.

Database Typical interview shorthand Depends on Mechanism Can you change it?
MongoDB Often CP-like Topology, replica-set election, readConcern / writeConcern Single primary per shard via replica-set election (Raft-like); writes go to primary. Primary reads are consistent relative to that primary, but not linearizable unless you also set readConcern: linearizable, which adds a majority-read round trip readPreference: secondaryPreferred trades consistency for availability/latency on reads; readConcern/writeConcern levels are the real CP/AP-ish knobs, not "primary vs secondary" alone
Cassandra Often AP-like Replication factor + chosen consistency level (ONE, QUORUM, ALL) Leaderless, any replica accepts writes; tunable consistency levels Yes, per-query — QUORUM reads+writes narrows the staleness window at the cost of latency; ONE is fully AP. QUORUM is not the same guarantee as a consensus-backed CP system — it narrows staleness, it doesn't provide linearizability
PostgreSQL (with replicas) Often CP-like on the primary Topology, failover/fencing policy, sync vs async replication Single writer (primary). Synchronous replication blocks the write until a standby acks — this controls durability and latency, not partition behavior by itself Whether the system stays available during a partition depends on your failover policy: automatic failover needs a fencing/quorum mechanism (e.g. Patroni + etcd) to avoid two primaries after a split; without one, a naive setup can produce split-brain, which is worse than either CP or AP
Redis (Cluster/Sentinel) Topology-dependent Replication, failover, quorum/fencing Asynchronous replication by default; a failed-over replica can be missing the last few writes WAIT N timeout makes the client wait for N replicas to ack before treating a write as durable — it reduces the data-loss window on failover, it does not make Redis linearizable or turn the cluster into a CP system; split-brain during a partition is still possible without proper fencing
DynamoDB Often AP-like by default Per-request ConsistentRead; Global Tables remain eventually consistent Eventually consistent reads by default; ConsistentRead: true opts a single read into strong consistency within a region Yes — per-request, not global; cross-region behavior (Global Tables) is still eventually consistent regardless of this flag
etcd / ZooKeeper Often CP-like Quorum membership; read consistency mode Raft/ZAB consensus — a write only commits after a majority quorum acks; a minority partition can't elect a leader or commit new writes Writes: no — quorum commit isn't optional, that's the entire point of a coordination service. Reads: yes — etcd defaults to linearizable reads (a quorum round trip, so it also blocks without a majority); opting into --consistency=serializable skips quorum for lower cost at the price of possibly-stale data. ZooKeeper's ordinary read is local-and-possibly-stale by default, and sync() before a read forces it to catch up to the leader first

Interview Insight 🎯

Typical interview shorthand — "MongoDB is CP, Cassandra is AP" — is table stakes for common configurations. The senior answer treats that as shorthand, not identity, and separates two different things that are easy to conflate: replication mode (sync/async) controls durability and latency, while partition behavior (what happens when nodes can't talk to each other) is actually determined by consensus/quorum and fencing — whether a minority side can still accept writes, and whether something prevents two nodes from both believing they're primary. "We use synchronous replication" answers a durability question; it doesn't by itself answer "are we CP or AP," which is why a system with synchronous replication but no fencing can still split-brain during a partition.

Run it yourself

The etcd row above is checkable, not just quotable: labs/etcd-cluster is a real 3-node Raft cluster where you kill a minority (writes keep working), kill a majority (writes refuse with a clean timeout, never corrupt), and run the exact --consistency=serializable vs. --consistency=linearizable comparison this table describes.


Architecture Diagram

sequenceDiagram
    participant Client
    participant NodeA
    participant NodeB
    Note over NodeA,NodeB: Normal operation — replication working
    Client->>NodeA: Write x=5
    NodeA->>NodeB: Replicate x=5
    NodeB-->>NodeA: ACK
    NodeA-->>Client: OK
    Note over NodeA,NodeB: ⚠️ Network Partition — replication broken
    Client->>NodeA: Write x=10
    Note over NodeA: CP system: reject to stay consistent
    NodeA-->>Client: Error (unavailable)
    Note over NodeA: AP system: accept, diverge from NodeB
    NodeA-->>Client: OK (stale data risk)

How It Works Internally

What is a Partition?

A network partition is when messages between nodes are lost or significantly delayed — not when a node crashes. Partitions can be:

  • A switch failure isolating one rack
  • High packet loss on an inter-datacenter link
  • A firewall rule change
  • Network congestion dropping packets
  • Elevated latency that crosses a timeout threshold — nodes are technically reachable, but slow enough that the system must treat them as partitioned anyway (a "gray failure," often harder to detect than a clean disconnect)

The dangerous case isn't the partition itself — it's what happens if both sides keep accepting writes without realizing the other side is still alive: split-brain, where two nodes each believe they're the leader, both accept writes, and the histories diverge in a way that isn't a simple merge. This is precisely what quorum-based consensus (Raft, ZAB) is designed to prevent — see Consensus & Raft for the mechanism.

Why Can't We Have All Three?

Proof sketch: 1. Two nodes, A and B. A receives write W1. 2. Before A replicates to B, a partition occurs. 3. Client queries B for the same key. 4. If we want Consistency: B must return W1 → B must wait → not Available 5. If we want Availability: B responds immediately → returns stale data → not Consistent


PACELC — The More Practical Model

CAP only covers the partition case. PACELC extends it:

If Partition (P): choose Availability (A) or Consistency (C). Else (E) — no partition: choose Latency (L) or Consistency (C).

Typical interview shorthand / common configuration — not a fixed product identity:

System Typical interview shorthand (partition) Typical interview shorthand (normal) Depends on
DynamoDB Often available (AP-like) Often low latency (EL) Per-request ConsistentRead; Global Tables stay eventually consistent
Cassandra Often available (AP-like) Often low latency (EL) Replication + chosen consistency level
MongoDB Often consistent (CP-like) Often low latency (EL) Topology, election, read/write concern
Spanner Typically consistent (CP-like) Typically consistent (EC) TrueTime + consensus by design, not a user CAP dial; stale reads only if you opt out of strong reads
HBase Typically consistent (CP-like) Typically consistent (EC) Typically CP via ZooKeeper region assignment, not Cassandra-style consistency levels

Interview Insight 🎯

PACELC is more useful in real design conversations than CAP alone because most distributed systems don't experience partitions often — the latency vs consistency trade-off (the "EL" part) dominates daily operation.


Read & Write Trade-offs

The PACELC "EL" axis isn't abstract — it shows up directly as a knob on every read and write:

Stronger consistency  ──────────────────────────────  Higher availability
  Slower writes                                          Faster writes
  Higher read latency                                     Lower read latency
  (wait for quorum/replica ack                          (ack immediately, replicate
   before returning)                                      in the background)
Strong consistency (quorum/sync) Eventual consistency (async)
Write path Block until majority of replicas ack — write latency = slowest replica in the quorum Ack after the local/primary write — replication happens after the client already has a response
Read path Route to primary, or read from a quorum and reconcile — extra round trip(s) Read from nearest/any replica — lowest possible latency, may be stale
Failure behavior A replica being slow or unreachable directly delays or fails the request A replica being behind just means it serves slightly old data — the request still succeeds
Cost Throughput ceiling = your slowest quorum member; you pay latency on every single operation Staleness window that's unbounded unless you add a mechanism (read-your-writes, bounded staleness) to cap it

This is why "consistency level" is usually a per-request parameter (Cassandra's ONE/QUORUM/ALL, DynamoDB's ConsistentRead) rather than a database-wide setting — a single system routinely runs both ends of this trade-off simultaneously: strong reads for a payment total, eventual reads for a "people also viewed" widget, against the same cluster.


Realistic Example

Designing a bank balance system:

Requirements: - Must always show correct balance (never show more money than exists) - Read/write the same account from multiple DCs

Choice: CP — we reject writes during partition rather than risk showing incorrect balances.

Implementation — and why quorum reads/writes alone are not enough: Quorum (write to 2/3, read from 2/3, overlapping majorities) guarantees a read sees the most recent acknowledged write — that's freshness, and it's genuinely useful. It does not guarantee a read-modify-write is atomic. Two overlapping quorum reads can both see balance=100, both compute "100 − 30 = 70" independently, and both quorum-write 70 — a classic lost update, even though every individual read and write was itself quorum-consistent (this is exactly the gap between quorum reads/writes, which are not linearizable by themselves, and true linearizability). A bank balance mutation needs one of:

  • Compare-and-swap / conditional writes (Cassandra's IF balance = :expected, a lightweight transaction) — the write only applies if the value hasn't changed since it was read, so a concurrent lost update fails the CAS and must retry instead of silently overwriting.
  • Consensus-backed writes (Raft/Paxos-replicated state machine — etcd, Spanner-style) — the whole read-modify-write goes through a single linearizable log, so concurrent mutations serialize correctly by construction.
  • A ledger design instead of a mutable balance — append immutable debit/credit entries (each one an independent, idempotent write) and compute balance as a sum/fold over entries, rather than mutating a single balance field at all. This sidesteps the lost-update problem entirely, because there's no shared mutable value for two writers to race on.

Plain quorum reads/writes are the right foundation for freshness and durability during a partition, but "CP + quorum" is not by itself a safe answer for a concurrent balance mutation — say so explicitly in an interview, because reaching for quorum alone here is exactly the mistake this example is warning against.

Designing a shopping cart:

Requirements: - Must always be accessible (losing a cart abandonment is expensive) - Slight staleness acceptable (cart merge on reconnection is fine)

Choice: AP — accept writes during partition, merge conflicts on reconnection (Last Write Wins or semantic merge).

Designing a chat/messaging system:

Requirements: - Users must always be able to send a message, even if a data-center link is degraded - Messages must eventually be delivered and ordered correctly per-conversation, but a few seconds of delay is invisible to the user

Choice: AP for the send path — accept the message locally, replicate and reorder asynchronously (this is exactly the trade-off WhatsApp-style messaging systems make: never block "send" on cross-region replication). Note the nuance: within a single conversation, causal/session consistency still matters — a reply shouldn't appear before the message it's replying to — so "AP" here means available-with-ordering-guarantees-per-conversation, not "anything goes."


Failure Modes

CP system during partition

  • Symptom: Clients receive errors / timeouts on writes
  • Impact: Revenue impact if the partitioned component handles customer-facing traffic
  • Detection: Error rate spike, timeout alerts
  • Mitigation: Multi-AZ deployment to reduce partition probability; circuit breakers to fail fast

AP system during partition

  • Symptom: Stale reads, conflicting writes, data anomalies post-partition
  • Impact: Inventory overselling, duplicate orders, lost updates
  • Detection: Read-your-writes violations, conflict resolution logs
  • Mitigation: Design for conflict resolution upfront; use CRDTs where possible

Production Debugging

When investigating consistency issues in a distributed system:

Symptom: User sees stale data after a write

1. Check replication lag
   → replica_lag metric, binlog position
2. Check if partition occurred
   → network error rate between nodes, packet loss
3. Check read routing
   → is the read going to a replica vs primary?
4. Check consistency level
   → Cassandra: QUORUM vs ONE, DynamoDB: strong vs eventual
5. Check for split-brain
   → are two nodes both accepting writes thinking they're primary?

Key metrics to monitor: - Replication lag (p50, p99) - Network packet loss between nodes - Write acknowledgment rate - Read-your-writes violation rate (application-level)


Scaling Limits

  • CP systems sacrifice availability during partitions → harder to scale writes globally
  • AP systems scale writes globally easily but require conflict resolution strategy
  • Spanner achieves global CP using TrueTime (atomic clocks + GPS) — extreme engineering cost

Trade-offs

Dimension CP AP
Consistency Strong Eventual
Availability during partition Degraded Full
Write throughput Lower (quorum) Higher
Conflict handling None needed Required
Operational complexity Medium High (merge logic)
Use cases Finance, config, coordination Social, carts, DNS, analytics

Interview Questions

Q: What is CAP theorem?

"CAP states that a distributed system can guarantee at most two of: Consistency (every read gets the latest write), Availability (every request gets a response), and Partition Tolerance (system works despite network failures). Since network partitions are unavoidable, the real choice is between C and A during a partition."

Q: How do you decide between CP and AP for a new service?

"I start with the data: what happens if two nodes accept conflicting writes and we can't reconcile them? For financial transactions — unacceptable, CP. For a shopping cart — a merge strategy handles it, AP is fine. I also consider access patterns: how frequently do partitions actually occur in our infrastructure? If we're single-region with good networking, partitions are rare, so the 'EL' part of PACELC (latency vs consistency during normal operation) matters more than the partition case."

Q: We're migrating from a typically CP-like system (PostgreSQL) to a globally distributed, often AP-like system (Cassandra) to reduce latency in APAC. What are the organizational and engineering risks?

"First, I'd challenge the premise — and the labels. PostgreSQL-with-replicas and Cassandra are not permanently CP and AP; both depend on topology and consistency settings. Why do we need global writes? Read replicas might get us 80% of the latency win without the consistency complexity. If we do proceed: we need to audit every write path for conflict sensitivity, design conflict resolution upfront (LWW is dangerous for inventory), ensure the application can handle 'eventual' — meaning UI, notifications, billing. Operationally, the team needs Cassandra expertise and tooling. I'd also set SLOs for read-your-writes guarantees and measure violation rates from day one. And plan the migration incrementally — start with non-critical writes."


Reasoning Exercises

  1. E-commerce inventory: 100 warehouses, each can decrement stock. Items should never go negative. Is this CP or AP? What's the conflict resolution strategy?

  2. Social media likes: Instagram shows like counts. Exact count matters less than availability. CP or AP? What consistency model do you use?

  3. Distributed config service (like etcd): Used by hundreds of microservices to read feature flags. CP or AP? What happens if the config cluster has a partition?

  4. DNS: You query a DNS server for an IP. The answer might be cached and 30 minutes stale. CP or AP? Why is this the right choice?


Key Takeaways

Remember

  1. Networks partition — it's not if, but when
  2. Partition Tolerance is mandatory in distributed systems; the choice is C vs A during a partition
  3. PACELC extends CAP: even without partitions, there's a latency vs consistency trade-off — and it shows up directly as slower/safer writes vs faster/riskier ones
  4. Most systems are "mostly CP" or "mostly AP" — it's a spectrum, not binary, and usually a per-request dial (Cassandra's consistency levels, DynamoDB's ConsistentRead) rather than a database's fixed identity
  5. Design for the failure mode explicitly: CP means failing loud; AP means merging conflicts
  6. Split-brain — two nodes both believing they're the leader — is the failure quorum-based consensus (Raft, ZAB) exists specifically to prevent

Previous: Distributed Systems Overview | Next: Consistency Models