What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks
When two replicas accept writes to the same key without coordinating, you get two values and no built-in notion of which came first — so every conflict-resolution scheme is really a way of manufacturing an order over writes that arrived without one. Last-write-wins orders by a scalar timestamp and keeps the max; Lamport clocks order by a counter that respects cause-and-effect but flattens everything into a single line; vector clocks keep one counter per writer so they can tell "A caused B" apart from "A and B happened independently," which is the only way to detect a conflict instead of silently discarding one side.
The choice is a trade between metadata cost and how much you care about losing a concurrent write. The three strategies below are the standard ladder from cheapest-and-lossy to expensive-and-lossless, followed by the two techniques (hybrid logical clocks, CRDTs) that production systems actually reach for.
Last-write-wins (LWW): order by a timestamp, keep the max
Each write is tagged with a timestamp — usually wall-clock microseconds — and on conflict the value with the larger timestamp wins; the loser is dropped. Apache Cassandra does exactly this per column: every cell carries a write timestamp, and a read that finds two cells for the same column returns the one with the newest timestamp (breaking exact ties by comparing the value bytes, so the result is deterministic but arbitrary).
It is the cheapest possible scheme: one number per write, resolution is a single comparison, no versions to keep, no merge to run. The cost is baked in — resolving by "latest" means throwing away every concurrent update but one. If Alice sets a field on replica R1 and Bob sets the same field on R2 within the same instant, one of them is deleted with no trace. And because the order is a physical timestamp, LWW is only as trustworthy as your clocks: a node whose clock runs fast wins conflicts it shouldn't, and an NTP step that moves a clock backward can make a genuinely newer write look older and be overwritten by stale data.
Vector clocks: one counter per node, so concurrency is visible
Each node keeps a vector of counters — one slot per writer, e.g. [A,B,C]. The rules are tiny: on a local write, a node increments its own slot; when it receives another node's version, it takes the element-wise max of the two vectors before applying its own increment. To compare two versions X and Y you use the partial-order rule:
X ≤ YiffX[i] ≤ Y[i]for every slot i.- If
X ≤ YandX ≠ Y, then X happened-before Y — Y already subsumes X, so Y wins cleanly, no conflict. - If neither
X ≤ YnorY ≤ X(each has at least one slot strictly larger than the other), the versions are concurrent — a real conflict. Neither causally knows about the other, so the system keeps both as siblings rather than guessing.
That last case is the whole point: a vector clock cannot merge data for you, but it can prove that two writes were independent, which LWW and Lamport clocks fundamentally cannot. Correction to a common myth: the original Amazon Dynamo paper (2007) and Riak used vector/version vectors to expose siblings — but the managed DynamoDB product does not; it resolves conflicts with last-write-wins by default. Don't cite "DynamoDB uses vector clocks."
Traced example: a shopping cart on three replicas
Vectors are [A,B,C]. A client adds items through whichever replica it reaches; replicas gossip writes to each other.
| Step | Action | Vector after | Cart value |
|---|---|---|---|
| 1 | Client adds milk via replica A → A bumps its own slot | A = [1,0,0] | {milk} |
| 2 | A's write replicates to B and C; both store version [1,0,0] | B, C know [1,0,0] | {milk} |
| 3 | Client adds eggs via B → merge max([1,0,0],[1,0,0])=[1,0,0], then bump B's slot | B = [1,1,0] | {milk, eggs} |
| 4 | Concurrently, client adds bread via C (never saw B's write) → bump C's slot | C = [1,0,1] | {milk, bread} |
| 5 | Read reconciles [1,1,0] vs [1,0,1]: slot B is 1>0, slot C is 1>0 → neither ≤ the other → CONCURRENT | siblings kept | {milk,eggs} & {milk,bread} |
Under LWW, step 5 would keep only whichever write had the larger timestamp — eggs or bread would vanish. The vector clock instead surfaces both siblings so the app (or a CRDT) can union them into {milk, eggs, bread}. Note the classic pathology: if the client always writes without first reading and merging, siblings pile up on every write — sibling explosion.
Lamport clocks: one counter, causal order but concurrency-blind
A Lamport logical clock is a single integer per process. On any local event a process does L = L + 1; when it sends a message it attaches L; on receive it sets L = max(L_local, L_received) + 1. This guarantees the one-way property: if event a happened-before b, then L(a) < L(b). The converse does not hold — a smaller timestamp does not mean it happened-before.
Trace showing the blind spot. Process P1 does event a: L=1, and sends a message carrying 1 to P2. P2 receives it: L = max(0,1)+1 = 2, event b. Separately, P3 — which never talked to anyone — does a local event c: L=1. Now compare b (2) and c (1): the numbers say c < b, implying c came first. But c and b are genuinely concurrent — no message ever linked them. Lamport's single counter has flattened two unrelated timelines into one arbitrary line. Using Lamport timestamps for conflict resolution is therefore functionally LWW (highest counter wins) — it just replaces the wall clock, so it dodges clock skew while inheriting LWW's silent data loss.
Beyond the three: HLC and CRDTs
Hybrid logical clocks (HLC) pack a physical timestamp and a small logical counter into one comparable value. They order events by wall time when clocks are close, and fall back to the counter to break ties and preserve causality when clocks disagree — giving you human-meaningful ordering plus a bound on how badly skew can hurt. CockroachDB and YugabyteDB use HLCs for exactly this. But HLC still assumes clock skew stays under a configured bound; if a node's clock jumps past that bound, the guarantee breaks.
CRDTs (conflict-free replicated data types) attack the other half of the problem. Vector clocks detect a conflict but leave the merge to you; a CRDT defines a merge that is commutative, associative, and idempotent, so replicas that saw the same writes in any order converge to the same value with no coordination and no lost update. A grow-only set (G-Set) unions its elements; an OR-Set tracks add/remove tags so a concurrent add and remove resolve deterministically. The shopping cart above is naturally an OR-Set: {milk,eggs} ⊔ {milk,bread} = {milk,eggs,bread}, automatically. The cost is that not every data type has a clean CRDT, and metadata (tombstones, causal context) accumulates.
Pitfalls a working engineer hits
- LWW + clock skew = a fast node wins everything. A replica whose clock is 200 ms ahead stamps larger timestamps, so its writes beat concurrent ones from correct nodes. Worse, an NTP correction that steps a clock backward can make a fresh write look stale and get silently overwritten by old data. Never derive LWW timestamps from unsynchronized wall clocks on the write path.
- LWW hides lost updates as "eventual consistency." The system looks healthy and converges — it just converged by deleting a customer's change. This is invisible unless you specifically instrument for it. Do not use LWW for anything additive (carts, counters, sets, audit trails).
- Vector-clock sibling explosion. If clients write without read-merge-write, every blind write forks a new sibling; a hot key can accumulate hundreds. Riak's dotted version vectors exist precisely to stop this. Always read-and-reconcile before writing.
- Vector clocks keyed by client actors grow unbounded. One slot per writer is fine for a fixed set of replicas, but if the actor is the client, the vector grows with your user base. Pruning old entries reclaims space but can erase causal history and cause false conflicts. Key vectors by server/replica, not by client, when you can.
- Lamport (or physical-clock) LWW gives false confidence. Because it always produces an order, engineers assume conflicts were "handled." They were merely hidden. If you need to know a conflict occurred, a scalar clock can never tell you — you need a vector.
When to use which — and what each costs
| Strategy | Metadata | Detects concurrency? | Data loss risk | Main cost |
|---|---|---|---|---|
| LWW (wall clock) | 1 timestamp | No | High — drops all but one | Clock skew corrupts ordering |
| Lamport clock | 1 integer | No | High (like LWW) | Flattens concurrency; no skew problem |
| Vector clock | N counters (per writer) | Yes → siblings | None if you merge siblings | Metadata grows with writers; you still write the merge |
| CRDT | Type-dependent (tags, tombstones) | Merges deterministically | None | Not all types fit; metadata accretes |
Concrete decision signals:
- Choose LWW when the field is a last-value-wins setting (theme, presence, cache entry), clocks are well-synchronized (single region, NTP or better), and a lost concurrent update is genuinely acceptable. It is the right default for a distributed cache — never for a bank balance or a cart.
- Prefer a Lamport clock over wall-clock LWW when you only need a consistent total order of events (a log, mutual exclusion, a serialization sequence) and want to eliminate clock-skew as a failure mode — but you still don't need to detect concurrency.
- Prefer vector clocks over LWW when updates are additive or independently valuable and you must not lose one, and you (or a client) can perform an application merge on conflict. Pay the metadata to make conflicts visible. This is the Dynamo/Riak multi-leader story.
- Prefer a CRDT over raw vector clocks when the data has a clean merge (sets, counters, registers, text) and you want convergence with zero coordination and zero human reconciliation. Vector clocks tell you a conflict exists; a CRDT makes the conflict not matter.
- Prefer HLC over any of the above when you want timestamps that are both human-meaningful (close to real time) and causally safe under bounded skew — the modern default inside CockroachDB / YugabyteDB.
Takeaways
- Every scheme imposes an order on uncoordinated writes; the axis of choice is metadata cost vs. tolerance for silently losing a concurrent write.
- Scalar clocks (LWW, Lamport) can order but can never detect concurrency — so they always risk dropping a real update; vector clocks add one counter per writer to make concurrency visible via the
≤partial order. - Vector clocks detect conflicts but do not resolve them; CRDTs supply the deterministic merge, and HLCs give skew-tolerant, human-meaningful timestamps.
- Get the facts right: Dynamo (the paper) and Riak used vector/version vectors; the DynamoDB product uses LWW. Cassandra is LWW by cell timestamp.
Re-authored/Deepened for this guide. Sources: Leslie Lamport, "Time, Clocks, and the Ordering of Events in a Distributed System" (CACM, 1978); DeCandia et al., "Dynamo: Amazon's Highly Available Key-value Store" (SOSP, 2007); Shapiro et al., "Conflict-free Replicated Data Types" (INRIA, 2011); Kulkarni et al., "Logical Physical Clocks" (HLC, 2014); Martin Kleppmann, "Designing Data-Intensive Applications" (ch. 5, replication & conflict resolution); Basho/Riak documentation on version vectors, siblings, and dotted version vectors; Apache Cassandra and CockroachDB documentation.
🤖 Don't fully get this? Learn it with Claude
Stuck on What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Progressively stronger hints — you still solve it.
I'm working on the problem **What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks** (System Design). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
See the technique, not just code.
Explain the optimal approach to **What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Catch bugs, edge cases, sub-optimality.
I'll paste my solution to **What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Lock in recognition with look-alikes.
Give me 2 problems that use the SAME underlying pattern as **What Are Common Conflict Resolution Strategies Last‑write‑wins, Vector Clocks, Logical Clocks**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.