If you remember five things
The whole topic in one glance. Everything below defends these five calls.
- Wall clocks lie. NTP still leaves tens of milliseconds of skew plus backward jumps, so physical timestamps cannot order events that land close together on different hosts.
- Order by causality, not time. Logical clocks track happens-before (did X possibly influence Y), which is the thing you actually care about.
- Lamport is one integer. It guarantees causally earlier means smaller number, gives a total order with a tie-break, but cannot tell you two events were concurrent.
- Vector clocks detect concurrency. One slot per actor, element-wise max on receive. Comparison reveals a genuine conflict, at the cost of size that grows with the number of actors.
- Concurrent is not the end. Detecting a conflict is step one. Resolution (siblings, CRDT merge, last-writer-wins) is a separate layer, and LWW loses data by design.
1The core idea#
Start with the problem that makes this whole topic exist.
Every machine has its own clock, and they drift.
- Three servers, three quartz crystals each ticking out "now."
- A cheap crystal loses or gains a few hundred milliseconds a day.
- Even with NTP, you still see tens of milliseconds of skew between machines.
- Sometimes a clock jumps backward when NTP corrects it.
So physical timestamps cannot order close events.
- Server A stamps an event at 10:00:00.250, server B stamps one at 10:00:00.180.
- You cannot conclude B happened first.
- B's clock might just be running 100 ms fast, which makes A's event the earlier one in real time.
- The lie is exactly the size of your clock skew.
Logical clocks throw out measuring real time and measure causality instead: did event X possibly influence event Y? If a user creates an order (X) and the payment service then reads that order and charges the card (Y), X must come before Y, because Y depended on X. That relationship is happens-before, written X -> Y.
Three rules define happens-before:
- Same process, X runs first: X -> Y.
- X sends a message, Y receives that same message: X -> Y.
- Transitive: if X -> Y and Y -> Z, then X -> Z.
Anything not on that chain is concurrent.
- Two users liking the same post on two servers, no message between them, are concurrent. Neither caused the other.
- Concurrent does not mean "at the same instant." It means "no causal path connects them."
- Two events can be concurrent even if one really happened a full second before the other on the wall clock, because wall-clock order is not what we track.
Concurrent means "no causal path," not "at the same time." Internalize that and half the confusion disappears.
2How it works#
Lamport clocks#
A Lamport clock is one integer per process. The rules are almost insultingly simple.
- Before any local event, increment your counter by 1.
- When you send a message, attach your current counter value.
- When you receive a message with timestamp
t, set your counter tomax(local, t) + 1.
That max is the whole trick. It pulls your clock forward so it is always ahead of anything that caused the message you just got.
The guarantee, stated precisely.
- If X -> Y, then LamportClock(X) < LamportClock(Y).
- Causally earlier events always get smaller numbers.
Reveal the answer
Treating the guarantee as an if-and-only-if. Clock order does not imply causal order for Lamport. If you claim clock 3 "happened before" clock 5 without knowing they share a causal chain, you have misread the one property that matters, and the interviewer now doubts the rest of your answer.
Vector clocks#
Vector clocks fix exactly that blind spot. Instead of one integer, each process keeps an array with one slot per process. On three nodes you carry [a, b, c].
- Before a local event, increment your own slot.
- On send, attach the whole vector.
- On receive, take the element-wise
maxof your vector and the incoming one, then increment your own slot.
Comparison now tells you the real relationship. Given vectors V and W:
- V -> W if every element of V is <= W and at least one is strictly less.
- Concurrent if neither is <= the other: one is ahead in slot
a, the other ahead in slotb.
Work two real examples.
[2,1,0]versus[2,3,0]: first is <= second in every slot (2<=2, 1<=3, 0<=0) and slotbis strictly less, so the first happened-before the second.[2,1,0]versus[1,2,0]: A leads in slot 0 (2 vs 1), B leads in slot 1 (1 vs 2). Neither vector dominates, so they are concurrent. Neither caused the other.
That second case is the detection Lamport cannot do. With plain Lamport integers those two writes might read 3 and 4, and you would wrongly believe 3 happened before 4.
When two versions of a value come back concurrent, the system knows it has a genuine conflict and either returns both to the client or applies a merge rule. A Lamport clock would have silently picked a winner and lost data. For a shopping cart that means an item the customer added just vanishes, which is the kind of bug that never shows up in a demo and always shows up in production.
3Trade-offs#
The core tension is size versus information. A Lamport clock is 8 bytes forever. A vector clock grows with the number of actors. Everything else follows from that.
Lamport clock
- Size
- One integer, fixed 8 bytes.
- Detects concurrency
- No.
- Best when
- You just need one agreed sequence, like an op log.
- Breaks when
- A wrong merge loses user data and you needed to see the conflict.
Vector clock
- Size
- One integer per actor, grows linearly.
- Detects concurrency
- Yes, that is the whole point.
- Best when
- Concurrent writes to the same key are normal and losing one is unacceptable.
- Breaks when
- You key on millions of clients and the vector bloats without bound.
The full comparison, for when the interviewer wants the table:
| Dimension | Lamport clock | Vector clock |
|---|---|---|
| Size | One integer, fixed 8 bytes | One integer per process, grows linearly |
| Detects concurrency | No | Yes |
| Gives total order | Yes, with tie-break | No, partial order only |
| X -> Y implies clock order | Yes | Yes |
| Clock order implies X -> Y | No | Yes |
| Overhead on each message | Tiny, constant | Grows with cluster size |
| Comparison cost | O(1) integer compare | O(N) element-wise compare |
| Typical use | Ordering, mutual exclusion, log sequencing | Conflict detection, causal consistency |
The vector clock's cost is real and it bites in practice.
- If "process" means "every client device," your vector can have thousands of entries, and most of that history is stale.
- Amazon's early Dynamo paper documented exactly this: vector clocks grew unbounded because they keyed on coordinator nodes.
- The fix they shipped was truncation (drop the oldest entries past some limit), which can produce false concurrency.
- That is the trade you accept: bounded size, occasional false conflicts.
A middle option worth knowing: server-keyed version vectors.
- Dotted version vectors and version vectors keyed by server, not client. Riak moved to this.
- Key the vector by the handful of servers that coordinate writes rather than by millions of clients.
- The vector stays small while still detecting the conflicts that matter.
- 5 coordinating servers means 5 slots, no matter how many of your 10 million users are writing.
My opinionated take.
- Reach for Lamport when all you need is one consistent order everyone agrees on, like sequencing entries in a replicated log.
- Reach for a vector clock when a wrong merge loses user data, like a shopping cart or a collaborative document.
- If you only need ordering and do not care about detecting concurrency, paying for vectors is waste.
4Where it is used#
Lamport clocks show up anywhere you need a deterministic total order cheaply.
- Distributed mutual exclusion algorithms use them to decide whose lock request came first.
- Replicated state machines use a Lamport-style sequence so every replica applies operations in the same order.
- Many message queues and append-only logs assign monotonically increasing sequence numbers that are Lamport clocks in spirit.
Vector clocks live where concurrent writes to the same key are normal and losing one is unacceptable.
- Dynamo and its descendants (Riak, early Voldemort) used them so the client could reconcile divergent versions.
- Collaborative editors and offline-first mobile apps use vector clocks (or CRDTs built on them) so two people editing while disconnected can merge cleanly on reconnect.
Name-drop hybrid logical clocks (HLC). They combine a logical counter with a physical timestamp, so the value is close to real wall-clock time and respects causality at once. CockroachDB uses HLCs. When an interviewer pushes "but I want timestamps that look like real time," HLC is the one-word answer that lands.
5Common pitfalls and gotchas#
Each of these has sunk real candidates in real interviews.
- Thinking equal Lamport timestamps mean concurrent. They do not. Equal values just mean the tie-break (process ID) decides, and unequal values do not prove causality.
- Incrementing after the event instead of before. Off-by-one here breaks the happens-before guarantee. Increment first, then act or send.
- Letting vector clocks grow forever. Keying on clients instead of servers is the classic mistake. Over months the vectors bloat and dominate your payload and storage. Decide keying strategy early.
- Assuming truncation is free. Pruning old vector entries to cap size can turn a real causal relationship into apparent concurrency, producing false conflicts the application must handle. That is a correctness trade, not just performance.
- Confusing logical order with the right business order. Clocks tell you what could have influenced what. They do not know the manager's edit should beat the intern's. Business conflict resolution is a layer above the clock.
- Expecting them to solve consensus. Logical clocks order events. They do not get nodes to agree on a decision. That is consensus (Paxos, Raft), a different tool.
6How it shows up in interviews#
Interviewers reach for this topic to see whether you understand why distributed order is hard, not whether you memorized the increment rules. The rules are the easy part.
The opening probe: "why not just use timestamps?"
- Weak answer: "Because clocks are not synced." True but shallow. It invites the follow-up "so just run NTP," which you then cannot answer.
- Strong answer: "NTP leaves tens of milliseconds of skew and occasional backward jumps, so two events 5 ms apart on different hosts cannot be reliably ordered. Order by physical time and the write with the smaller timestamp wins, which can silently be the write that actually came second, so I lose the newer data. I want to order by causality, not by a clock that lies by its own skew."
The follow-up: "okay, Lamport or vector here?"
- Weak answer: picks one immediately, usually "vector clocks, they are more powerful." That is reciting, not reasoning, and it ignores the cost.
- Strong answer: asks what the system does with the order first. "If the job is detect and resolve conflicting writes, I need concurrency detection, so vector or server-keyed version vectors. If the job is keep a single agreed sequence like an op log, Lamport is cheaper and enough, 8 bytes versus an N-slot array." Naming the cost of vector growth, and offering server-keyed version vectors or HLC as the refinement, is what separates senior from junior.
The curveball: "your vector clock says two writes are concurrent. Now what?"
- Weak answer: stops at "there is a conflict." That leaves the hardest part undone.
- Strong answer: goes to the resolution layer. "Options are return both versions as sibling values and let the client reconcile, apply a CRDT merge so the result is deterministic, use last-writer-wins with a documented tie-break, or escalate to the application. I would flag that LWW loses data by design, so I only use it when dropping one of two concurrent writes is genuinely fine, like a presence status, never for a cart or a balance."
The signal interviewers want: you treat ordering and agreement as separate problems, you quote real skew numbers, and you reason about the size cost of vectors instead of reciting their definition.
"Why not just run NTP everywhere and use wall-clock timestamps?"
"Two Lamport timestamps are 3 and 5. Did the first cause the second?"
"Your vector clock comparison returns concurrent. What do you do?"
"Vector clocks grow with actors. How do you keep that bounded?"
"I want timestamps that look like real time but stay causally correct."
What you can now explain in the room
- Why wall-clock timestamps cannot order close events across machines, with real skew numbers.
- What happens-before means and why concurrent means "no causal path," not "same instant."
- How a Lamport clock works, and why smaller number does not prove causal order.
- How a vector clock detects genuine concurrency that Lamport silently hides.
- The size-versus-information trade, plus server-keyed version vectors and truncation as the ways to bound it.
- Why detecting a conflict and resolving it are separate problems, and why LWW loses data by design.
Ready to apply this? Try the walkthrough on designing a conflict-free shopping cart for a multi-region store, where vector clocks and merge strategy are the whole game.
Get a prep plan built around your Logical Clocks: Lamport and Vector interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.