Skip to content
Consistency Models — Strong, Eventual, Causal Consistency and CRDTs Explained

Consistency Models — Strong, Eventual, Causal Consistency and CRDTs Explained

DodaTech Updated Jun 15, 2026 6 min read

A consistency model is a contract between a distributed data store and its clients that defines the allowed ordering of read and write operations, determining how quickly updates propagate across nodes.

Why Consistency Models Matter

Every distributed system must balance freshness against performance and availability. Banks need strong consistency — you can’t see a balance of $100 and withdraw $200 because a node hasn’t yet seen your deposit. Social media apps tolerate eventual consistency — it’s acceptable if a like takes a few seconds to appear globally. The choice of consistency model directly impacts user experience, system complexity, and operational cost. Distributed Systems like Amazon DynamoDB chose eventual consistency to achieve high availability at global scale.

Plain-Language Explanation

Imagine a group chat with three friends: Alice, Bob, and Charlie. When Alice sends “Let’s meet at 6pm,” Bob sees it immediately on his phone. Charlie was on a plane and receives it three hours later. During those three hours, Bob and Charlie have different views of the conversation. When Charlie finally lands, his phone catches up.

  • Strong consistency would mean Charlie can’t message anyone until he sees Alice’s message. The conversation pauses until everyone is synchronized.
  • Eventual consistency means Charlie eventually sees everything, but there’s a period where his view is stale.
  • Causal consistency would ensure that if Bob replies “Sounds good” after Alice’s message, Charlie sees “Sounds good” only after seeing Alice’s original message — maintaining cause and effect.

graph LR
    subgraph "Consistency Spectrum"
        Strong[Strong
High latency
Strict ordering] Eventual[Eventual
Low latency
Convergence only] Causal[Causal
Causality preserved
Concurrent unordered] end Strong --> Causal Causal --> Eventual UseCase1[Banking, Payments] --> Strong UseCase2[Social Media, DNS] --> Eventual UseCase3[Collaborative Editing] --> Causal style Strong fill:#e74c3c,color:#fff style Eventual fill:#27ae60,color:#fff style Causal fill:#f39c12,color:#fff

Strong Consistency

Every read returns the most recent write. All nodes agree on the order of operations. Like a single-node database.

Implementation: Requires consensus (Paxos, Raft) or synchronous replication. A write is acknowledged only after a majority of nodes confirm.

When to use: Financial transactions, inventory management (don’t oversell), leader election, locking systems.

Cost: Higher latency. A write to a strongly consistent system spanning 3 data centers takes at least 1-2 network round trips between data centers.

Eventual Consistency

Given enough time without updates, all replicas converge. Stale reads are possible but temporary.

Implementation: Asynchronous replication. A write is acknowledged immediately and propagated in the background.

When to use: DNS records, social media feeds, CDN caches, product catalogs, any scenario where slight staleness is acceptable.

Cost: Risk of serving stale data. Conflict resolution is needed if concurrent writes happen on different nodes.

Causal Consistency

Preserves cause and effect. If operation A causes operation B, all nodes see A before B. Concurrent operations (neither caused the other) can be seen in any order.

Implementation: Uses vector clocks to track causality.

When to use: Collaborative editing (Google Docs style), social media comments (a reply should appear after the original post), user sessions.

# Vector clock implementation for causal consistency
class VectorClock:
    def __init__(self, node_id: str):
        self.node_id = node_id
        self.clock = {}

    def tick(self):
        self.clock[self.node_id] = self.clock.get(self.node_id, 0) + 1

    def update(self, other_clock: dict):
        for node, ts in other_clock.items():
            self.clock[node] = max(self.clock.get(node, 0), ts)

    def happens_before(self, other: 'VectorClock') -> bool:
        """Check if this clock happens before another"""
        for node, ts in self.clock.items():
            if ts > other.clock.get(node, 0):
                return False
        return any(ts < other.clock.get(node, 0) for node, ts in self.clock.items())

    def concurrent(self, other: 'VectorClock') -> bool:
        return not self.happens_before(other) and not other.happens_before(self)

# Example
alice = VectorClock("alice")
bob = VectorClock("bob")

alice.tick()  # Alice edits document
print(f"Alice's clock: {alice.clock}")

bob.update(alice.clock)  # Bob receives Alice's update
bob.tick()  # Bob makes his edit
print(f"Bob's clock: {bob.clock}")

print(f"Alice happens before Bob? {alice.happens_before(bob)}")
print(f"Concurrent? {alice.concurrent(bob)}")

Expected output:

Alice's clock: {'alice': 1}
Bob's clock: {'alice': 1, 'bob': 1}
Alice happens before Bob? True
Concurrent? False

Read-Your-Writes Consistency

After a client writes, its subsequent reads always see that write. Other clients may not see it yet.

Implementation: Track the timestamp of the client’s last write and ensure reads go to a node with that timestamp or later.

When to use: User profile updates, account settings. The user who changed their password should immediately see the change when they reload the page.

CRDTs (Conflict-free Replicated Data Types)

CRDTs are data structures that automatically resolve conflicts without coordination. Two replicas can be updated independently and merged automatically.

# Grow-only Counter (G-Counter) — a simple CRDT
class GCounter:
    def __init__(self, node_id: str):
        self.node_id = node_id
        self.counts = {}  # node_id -> count

    def increment(self):
        self.counts[self.node_id] = self.counts.get(self.node_id, 0) + 1

    def value(self) -> int:
        return sum(self.counts.values())

    def merge(self, other: 'GCounter'):
        for node, count in other.counts.items():
            self.counts[node] = max(self.counts.get(node, 0), count)

# Two replicas
replica_a = GCounter("node-a")
replica_b = GCounter("node-b")

replica_a.increment()  # {node-a: 1}
replica_a.increment()  # {node-a: 2}
replica_b.increment()  # {node-b: 1}

# Network partition heals — merge
replica_a.merge(replica_b)
replica_b.merge(replica_a)

print(f"Replica A value: {replica_a.value()}")
print(f"Replica B value: {replica_b.value()}")

Expected output:

Replica A value: 3
Replica B value: 3

Choosing the Right Model

RequirementRecommended ModelExample
Financial accuracyStrongPayment processing
High availability > freshnessEventualSocial media likes
User expects own data immediatelyRead-your-writesProfile settings
Collaborative without conflictsCRDTsGoogle Docs
Causality matters, latency doesn’tCausalComment threads

Common Mistakes

  1. Using strong consistency everywhere: It’s expensive and slow. Use the weakest model that meets your correctness requirements.

  2. Ignoring client-side consistency: Read-your-writes can be implemented at the client (track timestamps) without server-side strong consistency.

  3. Not testing what happens during partitions: In an eventually consistent system, test: what does the user see during a 5-second, 30-second, 5-minute partition?

  4. Overlooking concurrent write conflicts: Without CRDTs or consensus, concurrent writes can cause conflicts that must be resolved (last-write-wins, custom merge, or manual resolution).

  5. Assuming monotonic reads: Without careful configuration, a client might read an older value after reading a newer one (non-monotonic). Use consistent routing or client-side timestamps.

Practice Questions

  1. What is the tradeoff between strong and eventual consistency? Strong consistency guarantees fresh reads but adds latency (need majority confirmation). Eventual consistency reduces latency but allows temporary stale reads.

  2. How do CRDTs resolve conflicts? CRDTs use mathematical properties (monotonicity, commutativity) so all replicas converge to the same state when merged, regardless of order.

  3. What is causal consistency in simple terms? If event A causes event B, everyone sees A before B. Events that didn’t cause each other can be seen in any order.

  4. When would you use read-your-writes consistency? When a user should immediately see their own changes (profile update, password change, comment they just posted), even if other users don’t see it yet.

  5. Why is strong consistency expensive in distributed systems? It requires all or majority of nodes to agree on each write before responding, which takes network round trips. For data centers on different continents, this adds hundreds of milliseconds.

Mini Project

Build a CRDT-based collaborative counter:

import copy

class PNCounter:  # Positive-Negative Counter (supports increment and decrement)
    def __init__(self, node_id: str):
        self.node_id = node_id
        self.pos = {}  # Positive counts per node
        self.neg = {}  # Negative counts per node

    def increment(self):
        self.pos[self.node_id] = self.pos.get(self.node_id, 0) + 1

    def decrement(self):
        self.neg[self.node_id] = self.neg.get(self.node_id, 0) + 1

    def value(self) -> int:
        return sum(self.pos.values()) - sum(self.neg.values())

    def merge(self, other: 'PNCounter'):
        for node, count in other.pos.items():
            self.pos[node] = max(self.pos.get(node, 0), count)
        for node, count in other.neg.items():
            self.neg[node] = max(self.neg.get(node, 0), count)

# Simulate two nodes with concurrent operations
n1 = PNCounter("node-1")
n2 = PNCounter("node-2")

n1.increment()  # +1
n1.increment()  # +1
n2.decrement()  # -1 (concurrent)

n1.merge(n2)
n2.merge(n1)

print(f"Final value: {n1.value()}")  # 2 + (-1) = 1

Expected output:

Final value: 1

Cross-References

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro