Short answers to what readers ask most about this topic.
01What is eventual consistency in simple terms?
Eventual consistency means replicas of the same data may disagree for a short time, but if no new writes arrive they all end up with the same, last-written value. It promises where the data ends up, not what a reader sees in the meantime. Systems accept this to keep writes fast and available.
02When is eventual consistency acceptable?
It is acceptable when a briefly stale read is cheap to be wrong about and concurrent writes have a merge rule, such as feeds, like counts, analytics and profile fields. It is not acceptable for balances, stock at the moment of sale, or uniqueness checks. A good test is whether a decision made on a few-seconds-old value can cost money.
03What is the difference between strong and eventual consistency?
With strong consistency every read sees the latest acknowledged write, as if there were a single copy, at the price of coordination and lower availability during a partition. With eventual consistency reads can be stale or out of order until replicas converge. Session guarantees such as read-your-writes sit between the two.
04What are read-your-writes and monotonic reads?
Read-your-writes means a process that performed a write will observe it in any later read. Monotonic reads means once a process has seen a value, later reads never show an older state. Jepsen classes the first as sticky available and the second as totally available, and both apply per process, not across users.
05How do eventually consistent systems resolve conflicts?
Common approaches are last-write-wins, which is simple but can silently discard a newer edit if clocks are skewed, version vectors that detect concurrent writes and keep both for the application to merge, and CRDTs whose merge is commutative, associative and idempotent. Counters and sets are the usual CRDT examples.
Eventual Consistency Explained: When Is Stale Data Acceptable?
What eventual consistency guarantees, how replicas converge with LWW, version vectors and CRDTs, and which features can tolerate stale reads and which cannot.
Eventual consistency means that if no new writes arrive, every replica of an item eventually returns the same, last-written value. It is acceptable for feeds, like counts and profile fields, where a briefly stale read is harmless. It is not acceptable for balances, stock levels or uniqueness checks, which need strong consistency at the point of decision.
Picture a carwash ERP where the owner's dashboard shows today's wash count a few seconds behind the cashier tablet. Nobody notices, nobody is harmed. Now picture the same few seconds of lag on the number that says three bottles of shampoo are left, with two cashiers selling the last ones at once. Same mechanism, completely different consequence.
This post answers the question people search for: what is eventual consistency and when is it acceptable? It covers the spectrum from strong to eventual, the session guarantees that make eventual systems feel sane, how replicas resolve conflicts, and a decision checklist. Definitions come from Werner Vogels, the Jepsen consistency models and Wikipedia; the numbers are worked arithmetic or output from a script I ran.
What is eventual consistency?
Vogels defines it as a guarantee that if no new updates are made to an object, eventually all accesses will return the last updated value. Read that carefully. It promises where replicas end up, not what any reader sees on the way there. During the gap, two readers can get two different answers, and the same reader can even get an older answer after a newer one.
The reason systems accept this is cost. Waiting for every replica to confirm each write makes writes slow and makes them fail when a replica or a link is down. Acknowledging a write locally and propagating it in the background keeps writes fast and available, and pays for it with a window of disagreement. I wrote about why that trade is forced by network partitions in the CAP theorem post, and about the replica lag that produces the window in the read replicas post, so here the focus stays on the concept and the decision.
Where does it sit between strong and eventual consistency?
Consistency is a spectrum, not a switch. The useful way to place a system is by what a reader is promised during the gap. The table lists the points I use when reviewing a design.
Model
What the reader is promised
What it costs
Typical fit
Strong (linearizable)
Every read sees the latest acknowledged write, as if there were one copy
Coordination on every write; slower, and unavailable on the minority side of a partition
Balances, stock at checkout, unique constraints
Session guarantees
A client sees its own writes and never sees time go backwards
Routing or version tracking per client; other clients may still be stale
Profile edits, settings, a user's own posts
Eventual
If writes stop, all replicas converge on the same value
Stale and out-of-order reads are possible; concurrent writes need a conflict rule
Feeds, view counts, analytics, caches
Strong eventual (CRDT)
Eventual, plus replicas that have seen the same updates hold the same state
Data must be modelled as a mergeable type; not every rule can be expressed
Counters, sets, collaborative editing
Notice that eventual consistency is the weakest row, not a different category. Most real systems sit in the second row: they are eventually consistent underneath and add session guarantees on top, which is the next question.
What are read-your-writes and monotonic reads?
Pure eventual consistency feels broken to users because it lets a person lose their own edit. Session guarantees fix the worst of that without full strong consistency. Two of them matter most in practice, and both are defined precisely by Jepsen and Vogels.
Read your writes: if a process performs a write and then a later read, that read must observe the write. Jepsen classes it as sticky available, meaning every node can keep making progress during a partition as long as the client keeps talking to the same server.
Monotonic reads: once a process has read a value, later reads cannot observe a state prior to it, so reads never go backwards. Jepsen classes it as totally available, so it survives a partition on any node.
Session consistency: Vogels describes it as read-your-writes holding for as long as the session exists, with no promise carried into a new session.
Both guarantees are per process. They say nothing about what a different user sees, which is exactly why a feed can show your new comment to you while a friend still sees the old thread. That is acceptable for a comment and unacceptable for a payment.
Sticky routing is the cheapest way to buy both guarantees: pin a user to one replica, or to the primary for a short window after they write. It gives the user a coherent view without making the whole system strongly consistent.
How do replicas converge when writes conflict?
Convergence needs a rule for what to do when two replicas accepted different writes to the same item. Wikipedia lists last writer wins as a widespread reconciliation approach, alongside anti-entropy exchanges and read repair. The script below runs two of the rules side by side: last-write-wins on a text field, and a CRDT counter on a stock number.
// convergence.ts (run with: node convergence.ts on Node 22.18+, or npx tsx)
// Two replicas accept writes while partitioned, then exchange state.
type Lww = { value: string; ts: number; node: string };
type PnCounter = { inc: Record<string, number>; dec: Record<string, number> };
// LWW register: highest timestamp wins; node id breaks ties so BOTH replicas
// pick the same winner. Without the tie-break they could swap values forever.
const mergeLww = (a: Lww, b: Lww): Lww =>
a.ts !== b.ts ? (a.ts > b.ts ? a : b) : a.node > b.node ? a : b;
// PN-counter: one slot per node, merged by taking the max of each slot.
// max is commutative, associative and idempotent, so merge order never matters.
const maxSlots = (x: Record<string, number>, y: Record<string, number>) => {
const out: Record<string, number> = { ...x };
for (const [n, v] of Object.entries(y)) out[n] = Math.max(out[n] ?? 0, v);
return out;
};
const mergeCounter = (a: PnCounter, b: PnCounter): PnCounter => ({
inc: maxSlots(a.inc, b.inc),
dec: maxSlots(a.dec, b.dec),
});
const total = (c: PnCounter) =>
Object.values(c.inc).reduce((s, v) => s + v, 0) -
Object.values(c.dec).reduce((s, v) => s + v, 0);
// Case 1: one field edited on both sides during the partition
const a: Lww = { value: "Jl. Merdeka 1", ts: 1000, node: "A" };
const b: Lww = { value: "Jl. Sudirman 9", ts: 1004, node: "B" };
console.log("LWW A merges B ->", mergeLww(a, b).value);
console.log("LWW B merges A ->", mergeLww(b, a).value);
// If B's clock runs 10 ms behind, its later edit carries ts 994 and loses silently:
const bSkewed: Lww = { ...b, ts: 994 };
console.log("LWW skewed B ->", mergeLww(a, bSkewed).value, "(B's newer edit is lost)");
// Case 2: stock starts at 10; A sells 3 and B sells 4 while partitioned
const base: PnCounter = { inc: { A: 10 }, dec: {} };
const left: PnCounter = { ...base, dec: { A: 3 } };
const right: PnCounter = { ...base, dec: { B: 4 } };
const lwwTotal = 10 - 3; // LWW on a plain number: A's write (7 left) wins, B's 4 sales vanish
console.log("LWW counter ->", lwwTotal, "left (true answer is 3)");
const m1 = mergeCounter(left, right);
const m2 = mergeCounter(right, left);
console.log("CRDT counter A<-B =", total(m1), " B<-A =", total(m2), " idempotent =", total(mergeCounter(m1, m1)));
I ran it and the output is shown here unedited. Both replicas pick the same address, which is convergence. But LWW is only as good as the clocks: shift one replica by 10 ms and the genuinely later edit is silently discarded. The stock case is worse, because LWW on a plain number keeps one side's value, so B's 4 sales vanish and stock reads 7 left when the true answer is 10 minus 3 minus 4, which is 3.
LWW A merges B -> Jl. Sudirman 9
LWW B merges A -> Jl. Sudirman 9
LWW skewed B -> Jl. Merdeka 1 (B's newer edit is lost)
LWW counter -> 7 left (true answer is 3)
CRDT counter A<-B = 3 B<-A = 3 idempotent = 3
The counter converges to 3 in either merge order, and merging a state with itself changes nothing. That follows from the requirement Wikipedia states for state-based CRDTs: the merge function must be commutative, associative and idempotent. Version vectors are the third tool. Each replica keeps a counter per node, and one write supersedes another only if its vector is greater or equal everywhere. Take A at A:2, B:0 and B at A:1, B:1. Neither dominates, so the writes were concurrent, and the system keeps both as siblings for the application to merge instead of guessing a winner.
LWW never reports a conflict. It converges, so every dashboard looks healthy, while data is being thrown away. Use it only for fields where losing one of two concurrent edits is genuinely fine, and never on a number that is supposed to add up.
Which features tolerate eventual consistency and which do not?
The deciding question is not how important the data is. It is what happens if one reader acts on a value that is a few seconds old. Here is how that plays out for features I would meet in ERP, POS and web apps.
Feature
Tolerates stale reads?
Why
What to do
News or activity feed
Yes
A post appearing seconds late harms nobody
Eventual, with read-your-writes for the author
Like or view counter
Yes
Approximate is fine, but increments must not be lost
CRDT counter or batched atomic increments
Profile or settings edit
Mostly
The editor must see their own change; others can lag
Read-your-writes, LWW acceptable
Account balance or payment
No
A stale balance allows an overdraft or a double spend
Strong consistency on the authoritative ledger
Stock at checkout
No
Two sellers can each sell the last unit, as in the 10 minus 3 minus 4 example
Atomic decrement or reservation at the primary
Unique username or invoice number
No
Two replicas can each accept the same value
Unique constraint enforced in one place
The pattern is that reads are cheap to be wrong about, and decisions are not. A stock figure shown on a product page can be eventual. The same figure at the moment of sale cannot. Most systems need both, so the real design choice is where the strong boundary sits, not whether to have one.
How do you decide whether eventual consistency is acceptable?
Run the feature through these five questions in order. The first no sends you to strong consistency for that path.
If a reader acts on a value that is a few seconds old, can money, stock or a legal record go wrong?
Does the feature enforce a uniqueness or ordering invariant, such as one holder per username or gapless invoice numbers?
Can two replicas accept writes to the same item at once, and what is the rule when they do?
Does the user who writes need to see their own change straight away? If so, add read-your-writes.
Can the screen honestly say the value may be slightly out of date, or does it promise an exact figure?
A yes to the first two means strong consistency for that write path, even if everything around it is eventual. If you answer no to those and have a merge rule for the third, eventual consistency is a sound choice, not a compromise.
What UI patterns make stale data safe?
Eventual consistency is a property of the storage layer, but users meet it in the interface, and the interface decides whether the gap feels like a bug. These patterns hide the gap or make it honest.
Optimistic updates: show the user's own change immediately from local state, which gives read-your-writes at the screen level, and roll back with a clear message if the server rejects it.
Pending states: mark a record as saving or syncing until the write is confirmed, so a missing item reads as in progress and not as lost.
Honest staleness: label dashboards with an updated-at time and show approximate counts as approximate, rather than a precise-looking number that may be wrong.
Re-check at commit: when the user presses buy or pay, read the authoritative value from the strongly consistent store and show a clear message if it changed.
The last pattern is the one that lets the rest of the page stay eventual. The cheap, fast, possibly stale read drives browsing, and one strong check guards the action that cannot be undone.
Eventual consistency is acceptable when a stale read is cheap to be wrong about and every concurrent write has a merge rule you chose on purpose. It is not acceptable for balances, stock at the moment of sale or uniqueness. Keep the strong boundary small, add read-your-writes for the person who just wrote, and never let last-write-wins touch a number that has to add up.