# The CAP Theorem for Working Developers

> During a network partition, distributed systems cannot guarantee both linearizable operations and availability to every non-failed node. Learn how that limit affects PostgreSQL replication, read routing, and the guarantees your .NET application needs for each operation.

Published: 2026-10-27. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/cap-theorem-dotnet-developers

The **CAP theorem** says a distributed system cannot guarantee both linearizable operations and availability to every non-failed node during a network partition.
For a .NET application, the practical question is which operations must wait or fail when replicas cannot coordinate, and which can return stale data or accept updates for later reconciliation.

## What Does the CAP Theorem Say?

The three terms have specific meanings:

- **Consistency** means linearizability: operations behave as if there were one copy of the data, with an order that respects operations that have already completed.
- **Availability** means every request received by a non-failed node eventually completes with a valid result. Returning an infrastructure error for every request does not satisfy it.
- **Partition tolerance** means considering executions where the network prevents nodes from communicating. The partition can last long enough that waiting for the other side prevents availability.

This consistency definition is different from the C in ACID, which concerns application invariants.
CAP availability is also different from a monthly uptime percentage or a promise that a response arrives within 200 milliseconds.

The contradiction is visible with two replicas.
A write completes at replica A while A and B cannot communicate.
A later read at B must either return without learning about that write, breaking linearizability, or wait/refuse the operation, giving up availability.

![During a partition that cuts node A from node B, a read at node B must either serve possibly stale data (AP) or refuse to answer until it can reach node A (CP)](https://milanjovanovic.tech/blogs/articles/cap-theorem-dotnet-developers/cp-vs-ap.png)

Eric Brewer's [**CAP retrospective**](https://www.infoq.com/articles/cap-twelve-years-later-how-the-rules-have-changed/) explains why "pick two" is an oversimplification and why applications can make different choices for different operations.
The theorem does not prescribe one database or one policy for an entire application.

## Why "Pick Two" Is Misleading

A distributed deployment cannot configure the network never to partition.
It must define what happens when nodes lose contact.
Choosing not to handle that case does not eliminate it.

During a partition, an operation requiring coordination may stop on one side while another operation continues.
The majority side of a quorum system may remain available to its clients even though clients restricted to the minority side cannot complete writes.
That is useful availability in practice, but not CAP's availability requirement for every non-failed node.

A single database node avoids coordination between replicas.
It can still become unreachable or fail, so calling it "CA" is not a useful reliability guarantee.
The CAP tradeoff becomes relevant when the data or operations are distributed across nodes that can lose communication.

Short delays and process pauses complicate failure detection too.
A timeout tells the caller that it stopped waiting, not whether the remote operation committed.
That distinction matters for retries and **idempotency keys**, independently of how you classify a database.

## Apply It to a PostgreSQL Deployment

Consider [**PostgreSQL**](https://www.postgresql.org/docs/current/warm-standby.html) with one writable primary and an asynchronous read replica.
The primary acknowledges a commit without waiting for that replica to apply it.
A replica read can therefore return older data, including while the replication connection is unavailable.

That read path sacrifices freshness for the ability to answer from the replica.
It does not make the entire deployment "AP": the replica normally cannot accept writes, and clients unable to reach the primary may be unable to write at all.
Read routing, failover rules, and fencing of an old primary all affect the observed guarantees.

With synchronous replication, selected standbys participate in commit acknowledgment.
If the required acknowledgment cannot arrive, commits can wait, reducing availability.
That still does not mean every arbitrary replica read observes the new value.

In PostgreSQL, `synchronous_commit = on` waits for the required standby to durably flush the commit record.
`remote_apply` additionally waits for replay so it is visible to queries on the acknowledging standby.
The [**synchronous commit documentation**](https://www.postgresql.org/docs/current/runtime-config-wal.html#GUC-SYNCHRONOUS-COMMIT) distinguishes these modes.

Even `remote_apply` does not guarantee freshness from a different lagging replica or from a transaction using an older snapshot.
Strong read guarantees require the read path and transaction isolation to match the write and replication configuration.

This is why an architecture review should name the operation and configuration.
"GET reads a replica that can lag" is actionable.
"Postgres is CP" leaves too much unspecified.

## Replication Lag Without a Partition

A user updates their profile on the primary, then a read goes to a replica that has not applied that update.
The old profile appears even though the network is healthy.
CAP does not claim this must happen during normal operation; it follows from the chosen asynchronous replication design.

The **PACELC** formulation describes another useful tradeoff: during a partition, availability versus consistency; otherwise, latency versus consistency.
Waiting for remote coordination can increase latency even when nothing has failed.
Relaxing the read guarantee may avoid that wait, with staleness as the cost.

This is not a reason to weaken every read.
Choose the guarantee required by the operation, and verify it in the selected database rather than inferring it from a product label.

For [**read-your-writes behavior**](https://milanjovanovic.tech/blog/eventual-consistency-dotnet), return the committed result or route the user's subsequent reads to the authoritative write store.
A fixed "read primary for two seconds" policy is a heuristic unless you can prove replication caught up before switching back.
Version or replication-position tokens can provide a stronger condition where the database supports them.

Sticky sessions alone are insufficient.
Sending a user to the same application instance does not ensure that instance reads an up-to-date database replica.

## Decide Per Operation

Different operations can tolerate different failure behavior:

- **Catalog browsing:** returning cached descriptions may be acceptable. Expose freshness if outdated prices or availability would mislead the user.
- **Inventory reservation:** the operation must preserve the stock invariant. Use an atomic check-and-update at the authority that owns inventory, and fail or wait if it cannot safely decide.
- **Cart updates:** accepting concurrent updates requires a documented merge rule and durable storage. "Available" alone does not tell you which update wins.
- **Payment requests:** an unavailable provider can leave the outcome unknown. Use provider idempotency support and reconciliation instead of assuming a timeout means no charge occurred.

These are application requirements, not automatic consequences of assigning CP or AP labels.
An idempotency key prevents duplicate execution only when its storage and processing actually enforce that behavior.
Likewise, a primary database connection alone does not protect a business invariant from concurrent application requests.

## What CAP Does Not Solve

CAP does not replace transaction isolation, [**message ordering**](https://milanjovanovic.tech/blog/message-ordering-distributed-systems), retry design, or backup and recovery.
It also does not say every cross-service workflow needs a distributed transaction.

An [**outbox**](https://milanjovanovic.tech/blog/outbox-pattern-for-reliable-microservices-messaging) keeps a business write and the intent to publish together in one transaction.
A [**saga**](https://milanjovanovic.tech/blog/saga-pattern-dotnet) records progress through several local transactions and defines compensation.
Both still depend on storage and communication recovering, and both expose intermediate states to other operations.

Review those intermediate states explicitly.
An order awaiting inventory is a workflow state; a replica returning an old order status is a stale read.
They require different remedies even though users may describe both as "the data is inconsistent."

## Summary

Write down which operations must coordinate and what they do when coordination is unavailable.
Then check the database's replication, read routing, isolation, and failover settings against those requirements.

CAP explains a limit during partitions.
It does not tell you that an entire product is CP or AP, or that every operation needs the same consistency guarantee.

## Frequently asked questions

### What does the CAP theorem actually say?

A distributed system cannot guarantee both linearizability and completion of every request to a non-failed node during a network partition. It does not prescribe the latency or consistency tradeoffs of normal operation.

### Why can I not just choose CA and skip partition tolerance?

A networked deployment must account for nodes losing communication. You can define which operations wait, fail, or continue with weaker guarantees, but omitting partition handling does not prevent partitions.

### Is PostgreSQL CP or AP?

There is no useful answer without the configuration and operation. Asynchronous replica reads can be stale, while synchronous replication can make commits wait for a standby. Read routing, isolation, and failover policies determine the guarantees.

### What is PACELC and why does it matter more than CAP?

PACELC adds the normal-operation tradeoff between latency and consistency to the partition-time tradeoff between availability and consistency. It helps explain why synchronous coordination can cost latency even without a network failure.
