Engineering Atlas › System Design Fundamentals

The CAP theorem and its trade-offs

14 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

The statement, precisely

Eric Brewer proposed CAP as a conjecture in 2000, and Seth Gilbert and Nancy Lynch proved it in 2002 (Gilbert and Lynch). Their model is a read-write register replicated across nodes. Consistency means linearizability: every operation appears to take effect at a single instant between its call and its return, so a read after a completed write sees that write or a later one. Availability means every request received by a non-failing node eventually gets a non-error response. Partition tolerance means the system keeps its guarantees even when the network drops any number of messages between nodes.

The proof is short. Split the nodes into two groups that cannot talk. A client writes on side 1; another then reads on side 2. If side 2 must answer (availability), it can only return what it has, which is the old value, breaking linearizability. If it must be linearizable, it cannot answer until the partition heals, breaking availability.

Why "pick two" is wrong

"CA" would mean consistent and available as long as there is no partition, which describes every system on a good day. Any network of more than one machine can partition (a switch reboots, a firewall rule changes, a process pauses in garbage collection), so a distributed system cannot opt out. The live question is how it behaves during the partition and how it reconciles afterwards.

Brewer himself wrote in 2012 that the "2 of 3" framing was always misleading, and that CAP's C is a strict subset of the C in ACID: it is about one register's recency, not about preserving every database rule (Brewer, InfoQ). He recommends treating a partition as an explicit mode with three steps: detect that it started, enter a partition mode that limits some operations, and run recovery when communication returns. Recovery is where the work hides, because both sides' writes must be merged and any broken invariants repaired.

"Effectively CA"

Google describes Spanner as technically CP: during some partitions it chooses consistency and gives up availability. Brewer argues it can still claim to be "effectively CA" because its measured availability is high enough, five nines or better, that users can ignore outages, and because partitions cause few of them. In Spanner's internal incident data, the network category was under 8%, and no Spanner quorum was ever on the minority side of a partition (Brewer, 2017). The lesson for everyone else: CAP's availability is an all-or-nothing property, while the availability users feel is a percentage you can engineer with redundant networks.

What the two choices look like

A consistent-under-partition system, such as etcd, ZooKeeper, or a Raft-replicated database, needs a majority quorum for every write and linearizable read. Minority-side nodes refuse or redirect requests; the majority side keeps working.

An available-under-partition system, such as Cassandra at low consistency levels, lets each side keep accepting reads and writes. Both sides may update the same key, so healing needs a merge rule: last-writer-wins, vector clocks with application merge, or CRDTs.

Per operation, not per database

Most databases let you choose per request: Cassandra's consistency levels (ONE, QUORUM, ALL), MongoDB's read and write concerns, DynamoDB's strongly consistent reads. The choice can also be per table. DynamoDB global tables default to multi-Region eventual consistency, where concurrent writes in two Regions are settled by last writer wins; since 2025 a table can instead be created with multi-Region strong consistency, which must span exactly three Regions (or two plus a witness), replicates each write synchronously to at least one other Region, and rejects a write to an item already being changed elsewhere with ReplicatedWriteConflictException (DynamoDB docs). The contention shape shows where C is required: the last seat, a bank balance, a unique username. Where staleness is harmless or repairable (view counts, a cart that merges both versions), A is the better choice. In an AI agent system, a task board that assigns work to exactly one agent needs C, while a cache of fetched web pages can be served stale.

PACELC: the everyday trade

Daniel Abadi's PACELC (Abadi, 2012) extends CAP: if Partition, choose Availability or Consistency; Else, choose Latency or Consistency. A linearizable write across three regions waits for a remote acknowledgement, a 60 to 150 ms round trip on every write. Partitions are rare; latency is constant, so the ELC half often matters more.

Down the stack

Layer 1The contract

A CP system promises: every acknowledged write is visible to every later read that succeeds, and during a partition, requests that cannot reach a quorum return an error or time out rather than a stale answer. An AP system promises: every request to a live node gets a response, and once communication is restored and writes stop, all replicas converge to the same value (eventual consistency), according to a stated merge rule. Jepsen tests both kinds of contract by injecting partitions and checking the recorded history.

Layer 2The mechanism

In Raft (used by etcd, Consul and CockroachDB), a leader appends each write to its log and commits it only when a majority has persisted the AppendEntries message. A leader on the minority side cannot commit. Plain Raft lets it go on believing it leads until it hears a higher term, so implementations add a quorum check: etcd's raft library steps a leader down automatically when it loses contact with a quorum (etcd raft). Linearizable reads go through ReadIndex, which confirms leadership with a heartbeat round before answering, or through leases, which skip that round but trust every machine's clock. In Dynamo-style stores, each key has N replicas; a write succeeds when W acknowledge and a read when R respond. With R + W > N (for example N=3, W=2, R=2), read and write sets overlap, which gives strong reads only when there are no concurrent writes and no sloppy quorums. Hinted handoff, read repair and Merkle-tree anti-entropy fix divergence later; Cassandra resolves conflicts per cell by write timestamp.

Layer 3The machine

A node cannot tell a partition from a slow peer, and TCP gives up slowly: with Linux's default tcp_retries2 = 15, an established connection can take around 15 minutes to report failure (Linux docs). So systems use their own timeouts: etcd's default heartbeat is 100 ms and its election timeout is 1,000 ms, with the guidance that the election timeout be at least 10 times the round-trip time (etcd tuning); Raft's paper suggests 150 to 300 ms election timeouts inside one data centre (Raft paper). Every write that needs a quorum pays at least one network round trip plus an fsync on a majority (a few hundred microseconds on NVMe, several milliseconds on cloud network disks). Last-writer-wins depends on wall clocks, and NTP-synced clocks typically drift by single-digit milliseconds, so two writes that close together may be ordered arbitrarily and one silently lost.

Why it's built this way

  1. Messages between machines can be lost or delayed without bound, so no node can know for sure whether its peers saw a write.
  2. Waiting for confirmation is the only way to be sure, so consistent systems require quorums and refuse to answer without one.
  3. Quorums cost a round trip on every write, and some users cannot afford to wait or to be refused, so other systems accept writes locally and repair divergence later.
  4. Repair requires deciding which concurrent write wins, so available systems carry timestamps, version vectors or CRDT metadata.

Diagram

sequenceDiagram
    participant C1 as Client A
    participant N1 as Node 1 (majority side)
    participant N3 as Node 3 (minority side)
    participant C2 as Client B
    Note over N1,N3: Network partition
    C1->>N1: write seat=Ana
    N1-->>C1: ok (Nodes 1 and 2 form a quorum)
    C2->>N3: read seat
    alt CP choice
        N3-->>C2: error: no quorum
    else AP choice
        N3-->>C2: seat=empty (stale)
    end
    Note over N1,N3: Partition heals: Node 3 catches up or merges

Worked example

A concert ticketing service stores seat assignments in a three-node cluster spread over three availability zones, and user profile data (display name, avatar, favourite artists) in the same cluster.

Zone C is cut off from A and B for 4 minutes during an on-sale with 20,000 seat requests per minute, a third of them routed to zone C.

With seats configured CP (quorum writes, linearizable reads), the node in zone C refuses seat writes. The load balancer's health check sees errors from zone C and shifts traffic to A and B within about 30 seconds. For those 30 seconds, roughly 3,300 requests fail and clients retry. No seat is sold twice.

Had seats been AP (local writes, last-writer-wins on heal), zone C would have kept selling for 4 minutes. If 1% of those roughly 26,700 sales collided with sales of the same seat on the other side, about 270 seats would be double-sold, and last-writer-wins would silently drop one buyer of each. Each needs a refund and an apology, far costlier than 30 seconds of retries.

Profiles are the opposite case. If a user edits their display name on both sides, one version wins on heal, which is acceptable, and CP would only add refusals and a quorum round trip to every update. The team sets profiles to consistency level ONE and seats to QUORUM in the same cluster.

In the wild

Trade-offs

Choice under partitionStrengthsWeaknessesChoose when
Consistency (quorum, refuse minority)No stale reads, no conflicting writes, simple application logicMinority side errors, higher write latencyMoney, inventory, uniqueness, locks, leader election
Availability (accept locally, merge later)Always answers, low latency, survives region isolationStale reads, conflicts, merge logic and its bugsCounters, carts, social data, caches, offline-first apps
Tunable per requestFit each operation to its needEasy to misconfigure, harder to reason aboutMixed workloads in one store
CRDT-based availabilityAutomatic, deterministic mergesLimited data types, metadata overheadCollaborative editing, counters, sets

Pitfalls in production

Leadership lens

Check yourself

  1. Why is "CA" not a meaningful option for a system replicated across data centres?

    Show answer

    Partitions will happen whether you choose them or not. Calling a system CA only describes its behaviour when the network is healthy; it says nothing about what it does when a partition arrives.

  2. In a 5-node Raft cluster split 2/3, which side can accept writes, and what do clients of the other side see?

    Show answer

    The 3-node side has a majority and keeps committing writes. Clients of the 2-node side get errors, timeouts or redirects until the partition heals.

  3. N=3, W=1, R=1. Is this CP or AP during a partition, and what can go wrong?

    Show answer

    It is available, since any single replica can answer. Reads can be stale and concurrent writes on both sides conflict, to be resolved by the store's merge rule.

  4. What does PACELC add that CAP leaves out?

    Show answer

    The trade during normal operation: strong consistency requires coordination on every write, which costs latency even when there is no partition.

Sources and further study

Primary

Systems docs

Books

Other curricula

Watch

Free resources

Open in the interactive app: practice, quiz, mock interview →