Vector clocks: an interactive conflict detection simulator
A vector clock is a list of counters, one per node, attached to each version of a piece of data. Comparing two vector clocks tells you whether one version descends from the other, so it can safely replace it, or whether the two were written concurrently, neither seeing the other, so both must be kept. Wall-clock timestamps cannot tell those apart, which is why systems that use them, last write wins, quietly lose writes.
The playground keeps a shopping cart on three replicas that each accept writes. Add items at different replicas, sync them in any order, and compare what vector clocks keep with what last write wins keeps.
A and B each add an item before hearing from the other. Their clocks, [A:1 B:0] and [A:0 B:1], are each ahead in one entry, so neither descends from the other: vector clocks keep both versions as siblings, while last write wins keeps the later timestamp and silently drops the other. Once every replica has synced: vector clocks keep both items, in 2 siblings for the next reader to merge; last write wins keeps 1, losing milk.
[A:1 B:0 C:0]stamped 12:00:11[A:0 B:1 C:0][A:1 B:0 C:0]stamped 12:00:12- A adds milk. New version [A:1 B:0 C:0].
- B adds eggs. New version [A:0 B:1 C:0].
- A → B: [A:0 B:1 C:0] and [A:1 B:0 C:0] are concurrent, neither has seen the other, so B keeps both as siblings. Last write wins keeps B’s cart and drops milk.
How vector clocks work
- Start at zero: every node’s entry is 0, written
[A:0 B:0 C:0]. - Write: take the clocks of the versions you read, combine them by taking the larger value of each entry, then add one to your own node’s entry.
- Compare: if every entry of one clock is less than or equal to the other’s, the second descends from the first and replaces it. If each is ahead in some entry, they are concurrent.
- Keep siblings: concurrent versions are stored side by side until someone reads them all, merges them and writes the result, whose clock descends from all of them.
In the second preset, A writes [A:1 B:0] and B writes [A:0 B:1]. A is ahead in its entry and B in its own, so neither descends from the other: two siblings. In the last preset, B reads both, merges them and writes [A:1 B:2], which is at least as large as both in every entry, so it replaces them.
Why not just use timestamps?
Last write wins keeps whichever version has the later timestamp and discards the rest. Two problems follow. Concurrent writes are not merged, one simply disappears: in the second preset the cart ends up with eggs but no milk. And the timestamps come from each node’s own clock, which drifts: in the slow clock preset, B’s write happens after A’s but is stamped earlier, so the newer write is the one thrown away.
Last write wins is still a reasonable choice when losing a concurrent write does not matter: a user’s latest location, a cache entry, a value that is always overwritten whole. Cassandra uses it for every column. For a shopping cart, a document or a counter, it loses data.
Siblings and merging
Detecting concurrency is half the job; something has to merge the siblings. Usually that is the application, on the next read: the Dynamo paper’s shopping cart took the union of the carts, so an added item was never lost, though an item removed in one sibling could reappear from another. Merges that are always safe are what CRDTs provide: data types such as sets, counters and maps designed so that concurrent versions combine automatically and every replica ends up the same.
Vector clocks, version vectors and Lamport clocks
- Lamport clock: a single counter. It orders events consistently with cause and effect, but two concurrent events still get different numbers, so it cannot tell you they were concurrent.
- Vector clock: one counter per process, so it can detect concurrency. Its size grows with the number of processes that write.
- Version vector: one counter per replica rather than per client, which keeps it small. This is what Dynamo-style stores track, and what the playground shows. Riak later moved to dotted version vectors, which avoid false siblings when many clients write through the same replica.
Clocks can still grow as nodes come and go; the Dynamo paper describes truncating the oldest entries once a clock passes a size limit, at the small risk of treating a descendant as concurrent.
Vector clocks in code
def compare(a: dict, b: dict) -> str:
keys = a.keys() | b.keys()
a_le_b = all(a.get(k, 0) <= b.get(k, 0) for k in keys)
b_le_a = all(b.get(k, 0) <= a.get(k, 0) for k in keys)
if a_le_b and b_le_a: return "equal"
if a_le_b: return "before" # b descends from a: b may replace it
if b_le_a: return "after"
return "concurrent" # keep both as siblings
def write(node: str, versions_read: list[dict]) -> dict:
clock = {}
for v in versions_read: # the new version has seen everything it read
for k, n in v.items():
clock[k] = max(clock.get(k, 0), n)
clock[node] = clock.get(node, 0) + 1
return clock
write("A", []) # {'A': 1}
write("B", []) # {'B': 1}: concurrent with A's
write("B", [{"A": 1}, {"B": 1}]) # {'A': 1, 'B': 2}: replaces bothIn a system design interview
Vector clocks come up when a design accepts writes in more than one place: a leaderless key-value store, an active-active multi-region database, offline-first sync. Say what you detect and how you resolve it: “Each value carries a version vector. When a write does not descend from the stored version, we keep both as siblings and return them on the next read; for the cart, the client merges by union.” Then expect the follow-ups: why not last write wins, how deletes are merged, and how big the vectors get.
Read the guide to CRDTs and operational transformation, try quorum reads and writes to see where concurrent versions come from, or watch a strong candidate design a distributed key-value store.
Questions people ask
What is a vector clock?
A list of counters, one per node, attached to each version of a value. A node writing a value takes the clocks of the versions it read, keeps the larger value of each entry, and increments its own entry. Comparing two clocks shows whether one version descends from the other or whether they were written concurrently.
How do you compare two vector clocks?
If every entry of clock A is less than or equal to the matching entry of clock B, then B descends from A and can replace it. If every entry is equal they are the same version. If A is larger in some entry and B in another, the versions are concurrent: neither writer had seen the other’s write, and both must be kept.
What is the difference between vector clocks and Lamport clocks?
A Lamport clock is a single counter that orders events consistently with causality, so if one event caused another it has a smaller number, but two concurrent events also get different numbers, so it cannot detect concurrency. A vector clock keeps one counter per node, which lets it tell causally ordered events from concurrent ones.
Why is last write wins dangerous?
It keeps the version with the latest timestamp and discards the others, so a concurrent write is lost without any error, and because timestamps come from each machine’s own clock, a node whose clock is behind can lose writes that really happened later. It is fine for values that are always overwritten whole, and wrong for carts, counters or documents.
What are siblings in a database?
Concurrent versions of the same key that a store keeps side by side because neither descends from the other. The next read returns all of them, the application merges them, for example by taking the union of two shopping carts, and writes the result with a clock that descends from every sibling, which replaces them.
What is the difference between a vector clock and a version vector?
Both are lists of counters compared the same way. A vector clock tracks events per process, often per client, and can grow large. A version vector tracks versions per replica, which keeps it bounded by the number of replicas; Dynamo-style databases use version vectors, and Riak uses a refinement called dotted version vectors.
Which databases use vector clocks?
Amazon’s Dynamo, as described in its 2007 paper, used vector clocks to detect conflicting versions of shopping carts, and Riak returns siblings using version vectors. Many other systems, including Cassandra, use last write wins with timestamps instead, and CRDT-based systems merge concurrent updates automatically.