Interview Prep Zoneby CuriouserLabs

Question 3 of 5 · staff level

Lamport timestamps vs vector clocks — what can each tell you, and what can't they?

🎤 Say this first

Lamport timestamps: each node keeps a counter, increments it per event, sends it with every message, and on receive sets counter = max(local, received) + 1. This gives a total order consistent with causality — if A happened-before B then L(A) < L(B) — but the converse fails: L(A) < L(B) does not mean A caused B, so you cannot detect concurrency, and therefore cannot detect conflicts. Vector clocks keep a counter per node and compare element-wise: A → B if every element of A ≤ B and at least one is strictly less; if neither dominates, the events are concurrent — a genuine conflict. So vector clocks answer the question LWW needs answered, at the cost of O(N) metadata per value, which is why version vectors (entries only for replicas, plus pruning) are what production systems actually ship.

The full picture

🎬 Vector clocks: telling 'stale' apart from 'concurrent'

1 / 5
P1P2P3Size is the cost: one counter per node, which is why production prunes to version vectors.

Three processes, each holding a counter per process: [P1, P2, P3]. A process bumps its OWN entry on every local event, and ships the whole vector with every message.

Three nodes, one message chain, one concurrent write — and the vectors that tell them apart.
LamportVector clock
Sizeone integerone integer per node
A → B implies L(A) < L(B)yesyes
L(A) < L(B) implies A → Bnoyes (element-wise)
Detects concurrencynoyes — neither vector dominates
Used fortotal order, ticket numbers, term numbersconflict detection, siblings, causal reads
  • Happens-before (→) is the actual concept these implement: A → B if they're on the same node in program order, or A is a send and B its receive, plus transitivity. Everything else follows from that one relation — define it first and both clocks become obvious.
  • The Lamport limitation stated as a failure: two unrelated writes on different nodes get different Lamport values, so a system using them for LWW will confidently pick a 'winner' between events that were genuinely concurrent, discarding the other with no signal. That's precisely the data loss vector clocks exist to expose.
  • Metadata growth is the real-world constraint: a vector with an entry per client is unbounded, which is why Riak switched to dotted version vectors keyed by replica, with pruning of old entries. The cost of pruning is rare false conflicts — acceptable, since a false conflict is merely extra work, while a missed conflict is data loss. That asymmetry is the design principle worth stating.
  • Where you meet logical clocks without noticing: Raft term numbers are Lamport clocks; Kafka offsets are a per-partition logical clock; a database row version for optimistic locking is a one-node vector clock. Naming these makes the theory feel like tooling you already use.
  • Interval tree clocks / HLC are the modern refinements — HLC especially, because it keeps causality and stays comparable to wall-clock time, which is what most engineers actually want when they reach for a timestamp.

🔄 Likely follow-up questions

  • Give two events with L(A) < L(B) that are actually concurrent.
  • Why do production systems key version vectors by replica rather than by client?
  • In what sense is a Raft term number a Lamport clock?