The question this answers
Is it safe to resolve conflicts by keeping the write with the later timestamp?
LWW guarantees convergence and nothing else: every replica ends up holding the same value, because "maximum" is commutative and associative. It does not guarantee that the surviving value is the most recent one in real time, that it reflects the last user action, or that any information is preserved from the discarded write. Convergence to an arbitrary survivor is still convergence, and that is the entire content of the guarantee.
Everything below is bought to hold this sentence. "Strongly consistent" with no scope attached is a slogan, not a guarantee — read what it actually covers, and what it explicitly does not.
A replica knows two values and two timestamps that were generated by clocks it has never compared. It does not know which write actually happened later, whether the two writes were concurrent or causally related, or that a write is about to be destroyed. From the replica's position, discarding the loser and applying a legitimate supersession are literally the same operation.
A node knows its own state and the messages that arrived. Everything else is inference from evidence that was already stale. "B has not replied in five seconds" is knowledge; "B is down" is a decision — and usually the bug.
The two problems, stated separately
LWW has two independent defects, and it is worth keeping them apart because fixing one does nothing for the other.
Problem one: "last" is not well defined. The comparison uses wall-clock timestamps produced on different machines, and those clocks disagree by an amount nobody can measure (Clock Skew: The Gap You Cannot Measure From Inside). The write that survives is the one whose *machine's clock ran fastest*, not the one that happened later. This is not a small bias: a host 200 ms fast wins every conflict against its peers, permanently and invisibly, so one datacentre's writes systematically survive and another's systematically vanish. Replacing the wall clock with a Lamport clock does not fix it either — then the *busiest node* wins instead (Lamport Clocks: Consistent With Causality, Blind to Concurrency).
Problem two: the loser is destroyed. Even with a perfect clock, LWW answers a question that has no answer. Two concurrent writes have no order (Happens-Before: The Only Ordering You Actually Have); imposing one does not discover which came first, it invents an answer and throws away the other write's content. There is no error, no conflict record, and usually no way to recover the discarded value. The user who wrote it received a 200 OK.
Put together: LWW is a mechanism for choosing which user's work to delete, using an arbitrary criterion, silently. That is a legitimate engineering choice for some data. It is a catastrophic default for most.
What LWW actually costs, with the arithmetic
It is easy to wave at "you might lose writes". The useful version is an estimate, because it turns an abstract risk into a number a team can argue about.
The window in which a conflict can be created is roughly the replication delay between replicas plus any partition duration. Any two writes to the same key inside that window are candidates for concurrency, and under LWW one of them is discarded. So the loss rate scales with write rate to contended keys × window duration, and it is heavily concentrated: the hot keys and the long partitions produce nearly all of it.
Two consequences follow that are worth stating plainly. First, LWW looks fine in testing and in steady state, and produces its damage in a burst during exactly the incidents when you are least able to notice. Second, the damage is unrecoverable by design — the discarded value was never stored anywhere, so there is no backup to restore from and no log to replay. Compare this to a system that keeps siblings, where the same incident produces a pile of conflicts you can resolve afterwards at leisure.
writes/sec to the contended range 120 mean replication delay 80 ms => steady-state concurrency window ~0.08 s => expected concurrent pairs/sec ~ (120 * 0.08) collisions on hot keys -> a handful per second, mostly on the top-N keys now a 20-minute partition, both sides live: writes accepted on side A ~144,000 writes accepted on side B ~144,000 overlapping keys touched on both (measure it; it is never zero) -> every overlap discards one write, silently, at heal time The number that matters is not the average. It is that the loss arrives in one burst, during an incident, on your most active data.
When LWW is genuinely the right answer
The rule is not always wrong, and treating it as universally forbidden is its own kind of sloppiness. LWW is correct when the discarded write carries no information you need, and there is a real class of data like that.
Cache entries, presence and status ("user is online"), sensor readings where only the newest matters, a heartbeat timestamp, a recomputed derived value, a "last seen" marker — for all of these, losing an intermediate value costs nothing because a newer value supersedes it in meaning as well as in storage. The key test is: would a user or an auditor ever want the discarded value back? If no, LWW is fine and cheaper than everything else.
It is also acceptable — with care — where writes for a key genuinely come from a single writer, so concurrency does not arise and the rule never fires. That reasoning is fragile, because it depends on a property nothing enforces. When someone later adds a second writer, LWW starts discarding data and nothing in the system objects.
And if you do use it, at least make the ordering defensible: derive the timestamp at a single point (the coordinating replica, not the client), break ties deterministically by node id so all replicas agree, and record a counter of discarded versions so the loss is *measurable* even when it is not preventable.
| Question | If yes | If no |
|---|---|---|
| Would anyone ever want the discarded value back?assumption | LWW is wrong — keep siblings or merge | LWW may be fine |
| Is the value a set, counter or accumulation?protocol | LWW is wrong — it loses additions ([[crdts]]) | Continue |
| Does an invariant span this write and another?protocol | LWW is wrong — you need coordination ([[protecting-invariants]]) | Continue |
| Can more than one writer touch this key?assumption | LWW will fire; make sure the loss is acceptable | LWW never fires — but nothing enforces that |
| Is a newer value strictly more useful than an older one?typical | LWW is a good fit | LWW is a poor fit |
The upgrade path, in order of cost
If LWW is wrong for a piece of data, the replacements are not all expensive. They form a ladder, and most teams can take the first step immediately.
Step one: detect. Add causal metadata so the system can tell a supersession from a conflict (Version Vectors: Making the Conflict Visible). Even if you continue to resolve by timestamp, you now have a *conflict counter*, and the loss stops being invisible. This alone frequently changes a team's mind, because the number is never what they expected.
Step two: keep both. Store concurrent versions as siblings and return them on read. The write path stays available and nothing is destroyed; the cost moves to the read path, which must now handle a set (Only the Application Knows What the Merge Means for who resolves it).
Step three: merge properly. Give the data type a real merge rule — union for sets, max for monotonic values, a domain rule for structures. Where the rule can be expressed as a join that is commutative, associative and idempotent, you have arrived at a CRDT and convergence comes for free (CRDTs: Deterministic Merge, Not Correct Merge, What "Eventually Converges" Actually Requires).
Step four, only if needed: coordinate. If no merge is correct because an invariant spans the writes, stop trying to merge and make the writes serialise through one owner (Start From the Invariant, Not From the Architecture, Do You Actually Need Consensus?). This is the expensive option and it should be the last one, applied to the smallest possible slice of data.
Key points
- LWW guarantees convergence only. It says nothing about the survivor being the right value.
- "Later" is decided by whichever machine's clock runs fastest — a systematic, invisible bias toward one host or region.
- Switching to a Lamport clock does not fix it; then the busiest node wins instead.
- The discarded write is destroyed with no error, no log and no recovery path. The user who wrote it saw a 200.
- The loss arrives in a burst when a partition heals — during an incident, on your hottest keys.
- It is genuinely correct for data where a newer value strictly supersedes an older one: caches, presence, latest-reading sensors.
- The cheapest improvement is not replacing it but *detecting* — add causal metadata and count what you are discarding.
The chain, answered
Every field here is required, which is why no lesson in this domain can recommend a design without naming what an operator sees when it fails, what survives the partition, what repairs it afterwards, and the simpler thing to consider first.
- • Each write is stamped with a timestamp, taken from the client, the coordinating replica, or the storage node.
- • When two versions of a key meet, the resolver compares the stamps and keeps the larger.
- • Ties are broken by some deterministic rule — usually node or version id — so replicas do not diverge.
- • The losing version is discarded immediately; it is generally not stored, logged or counted.
- • Because
maxis commutative and associative, all replicas converge to the same survivor regardless of the order versions arrive.
- • Clock skew makes the wrong write win, systematically and in one direction.
- • A clock step backwards causes a legitimate later write to be discarded as "older" than one it should supersede.
- • A client-supplied timestamp is trusted, letting any client win any conflict by sending a large number — accidentally or otherwise.
- • Two components break ties differently (string vs numeric id comparison) and replicas converge to different survivors.
- • A write far in the future is stamped by a badly skewed host and then wins every subsequent conflict until real time catches up — the "poisoned key" case, which can last for years.
- • A user's edit reverts with no error: they save, see success, refresh, and the old value is back. The operator finds two successful writes in the logs and no indication that either was discarded.
- • Regionally biased data loss: writes from one datacentre systematically survive and another's systematically vanish, because one region's hosts run fast. The operator sees complaint volume concentrated by geography with no infrastructure difference to explain it.
- • A key frozen in the future: a host with a broken clock stamps a value with a timestamp years ahead, and every subsequent legitimate write loses to it. The operator observes one key that "cannot be updated" while every write returns success.
- • Post-partition data disappearance: after a WAN link heals, a burst of writes accepted on the minority side vanishes at merge time. The operator sees no errors at all and a support queue that fills an hour later.
- • Counter drift: a value incremented concurrently on two replicas ends up reflecting only one side's increments, so a total is quietly low and stays low. Detected, if ever, by a reconciliation report rather than by any alert.
- • None — LWW's entire appeal is that it resolves with no communication, which is why it is the default in available systems.
- • The cheapness is real; the problem is the price is paid in data rather than in latency, and data loss does not appear on a latency dashboard.
- • Any alternative that preserves the losing write costs storage and read-path complexity rather than coordination — so the upgrade from LWW does not usually cost availability, which is the most under-appreciated fact in this lesson.
- • Convergence still holds under any failure: replicas agree on the survivor regardless of message order or partition.
- • What does not hold is any relationship between the survivor and reality, and that degrades precisely as clock quality degrades.
- • A skewed host does not merely produce a wrong answer during the skew — its writes keep winning for as long as their timestamps remain the largest.
- • Detect: add version metadata and a discarded-version counter, even if you keep resolving by timestamp. You cannot manage a loss you cannot count.
- • Contain: stop trusting client-supplied timestamps and stamp at a single point in the write path; drain hosts with large clock offsets.
- • Recover: for keys poisoned by a future timestamp, an explicit administrative overwrite is usually the only route, since ordinary writes cannot win.
- • Reconcile: where the data has a derivable source of truth (an event log, an upstream system), rebuild the affected keys from it (Reconciliation Is a Component, Not a Cleanup Script, Source of Truth: The Question Every Inconsistency Incident Is Really Asking).
- • Verify: run a partition test and confirm the count of discarded versions matches the count of writes you deliberately made on the losing side.
- • Count of versions discarded by the resolver, per key range. If this metric does not exist, that is the finding.
- • Distribution of winning replica or region across conflicts. A skew here is a clock problem wearing a data-loss costume.
- • Maximum stored timestamp versus current time, per key range — the detector for future-stamped poisoned keys.
- • Rate of client-supplied timestamps that are ahead of server time on arrival.
- • Post-partition burst size: discarded versions in the first minutes after a link heals, which is where nearly all the loss occurs.
- • Caches, presence, "last seen", sensor readings, and any value where newer strictly supersedes older in meaning as well as in storage.
- • Derived values that can be recomputed from a source of truth, where losing an intermediate costs nothing.
- • As a deliberate, documented choice for low-value high-volume data, where the alternative's storage cost genuinely outweighs the loss.
- • Anything a user typed. The discarded value was somebody's work, and they were told it was saved.
- • Any accumulation: counters, sets, carts, lists. LWW replaces rather than combines, so additions are lost wholesale (CRDTs: Deterministic Merge, Not Correct Merge).
- • Anything with an invariant spanning writes: balances, inventory, quotas. LWW does not merely lose data, it can produce a state no sequence of legal operations could reach.
- • Anything auditable, where "we cannot say what the other value was" is not an acceptable answer.
- • Keep concurrent versions as siblings and resolve on read — preserves everything, moves the work to the reader (Only the Application Knows What the Merge Means).
- • Detect conflicts with Version Vectors: Making the Conflict Visible so the loss becomes visible even if the rule stays the same. Cheapest possible first step.
- • Use a data type with a defined merge, so no version is ever discarded (CRDTs: Deterministic Merge, Not Correct Merge).
- • Use optimistic concurrency with a version precondition, converting a silent loss into a visible
409the client can retry against fresh state. - • Route writes for the key to a single owner so no conflict arises (Leader-Based Replication: Buying Order With a Single Writer, Hash Partitioning and the Modulo Trap).
- • If you must keep LWW, stamp at one point, break ties deterministically, refuse client timestamps, and count what you discard.
The write that won, and the write nobody will see again
real time 1000 ms A writes "A: draft 1" stamped 1000 ms (A's clock is 0 ms fast) real time 1300 ms B writes "B: reviewed 1" stamped 1300 ms last-write-wins compares 1000 and 1300 → B survives
| round | A wrote at (real) | A stamped | B wrote at (real) | B stamped | survivor | destroyed |
|---|---|---|---|---|---|---|
| 1 | 1000 | 1000 | 1300 | 1300 | B | A’s write |
| 2 | 2000 | 2000 | 2300 | 2300 | B | A’s write |
| 3 | 3000 | 3000 | 3300 | 3300 | B | A’s write |
| 4 | 4000 | 4000 | 4300 | 4300 | B | A’s write |
| 5 | 5000 | 5000 | 5300 | 5300 | B | A’s write |
| 6 | 6000 | 6000 | 6300 | 6300 | B | A’s write |
What people believe, and what is true
LWW keeps the most recent write.
It keeps the write with the largest number. Which one that is depends on clock skew, and the bias is systematic rather than random.
With NTP running, LWW is accurate enough.
Conflicts are generated by writes milliseconds apart — exactly the resolution at which NTP offers no useful guarantee. And the case that matters is a partition, when synchronisation is also degraded.
LWW is safe because it converges.
Convergence means all replicas agree. It says nothing about them agreeing on the right value. Deleting all data on every replica also converges.
We would notice if we were losing writes.
There is no error, no log line and no metric by default. Teams typically discover it from a support ticket months later, if at all.
Using logical clocks instead of wall clocks fixes LWW.
It removes the clock-skew bias and replaces it with a write-volume bias. The loser is still silently destroyed, which was the more serious of the two problems.
Go deeper
Only the levels this lesson can honestly fill — a missing level is a claim nobody had.
Overview
Keeping the write with the larger timestamp converges, and that is all it does. "Later" is decided by whichever clock is fastest, and the losing write is destroyed silently.
Practical
Find out what your store actually does. Add version metadata and a discarded-version counter before changing anything else — measurement usually settles the argument. Keep LWW only for data where nobody would want the discarded value back, and stamp server-side with a deterministic tiebreak.
Advanced
LWW is a join on a total order over (timestamp, id), which is why it converges: max is a semilattice join. The defect is that it embeds the causal partial order into a total one, and every pair of incomparable elements gets collapsed to one representative. A CRDT keeps the same algebraic property while choosing a join that preserves information — union rather than max — which is exactly the difference between converging with the data and converging without it.
Apply it
- 🔧 Instrument the resolver to count and sample discarded versions for one week. Present the number to the team before proposing any change.
- 🔧 Construct a test where a host with a fast clock wins a conflict against a genuinely later write, and assert the discarded value is recorded somewhere.
- ⚡ A key in production cannot be updated: every write returns success and the value never changes. What single query would confirm your hypothesis?
- ⚡ Support reports that customers in one region lose profile edits more often than another. Infrastructure is identical. Where do you look?
- 💬 What exactly does last-write-wins guarantee? Be precise.
- 💬 Name a data type where LWW is correct, and one where it is catastrophic, and say what distinguishes them.
- 💬 You inherit a system using LWW on user-editable fields. What is your first change, and why is it not "replace LWW"?