Home › High-Level Design › Logical Clocks: Lamport and Vector
L

Logical Clocks: Lamport and Vector

If you cannot trust wall-clock time across machines, you cannot say which event happened first. Logical clocks are how distributed systems agree on order without a shared clock. I have watched strong candidates freeze on "two writes land on different replicas, which one wins?" The honest answer starts here, and if you cannot give it, the interviewer quietly concludes you have only ever reasoned about single-machine systems.

High-Level DesignAdvanced11 min read

If you remember five things

The whole topic in one glance. Everything below defends these five calls.

  1. 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.
  2. Order by causality, not time. Logical clocks track happens-before (did X possibly influence Y), which is the thing you actually care about.
  3. 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.
  4. 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.
  5. 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.
The mental shift this whole topic hangs on

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 to max(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.

sequenceDiagram participant A as Process A participant B as Process B A->>A: event a1 clock 1 A->>A: event a2 clock 2 A->>B: send msg ts 3 B->>B: recv sets clock max 0 3 plus 1 equals 4 B->>B: event b1 clock 5
The worked example I would draw on the whiteboard. A climbs 1, 2, then sends timestamp 3 (the send is itself an event). B was at 0, computes max(0, 3) + 1 = 4, then its next event b1 goes to 5. Notice B leaps from 0 to 4: it never lives through 1, 2, 3, it just jumps ahead so everything it produces after the message outranks anything that caused it.

The guarantee, stated precisely.

  • If X -> Y, then LamportClock(X) < LamportClock(Y).
  • Causally earlier events always get smaller numbers.
Pause before you read on: you see event P at clock 3 and event Q at clock 5. Can you conclude P caused Q?
Reveal the answer
No. The reverse of the guarantee is false. Smaller number does not imply happens-before. P and Q might be concurrent events on different machines that just happened to land on 3 and 5. Lamport gives you a total order if you break ties by process ID, but that order silently invents an ordering between events that never influenced each other. Saying this out loud is the difference between understanding the clock and reciting it.
The red flag that sinks candidates here

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 max of your vector and the incoming one, then increment your own slot.
flowchart LR A[Node A increments slot a] --> M[Send vector a1 b0 c0] M --> B[Node B takes elementwise max] B --> C[Node B increments slot b] C --> D[(Causal history preserved)]
The vector update on a message hop. B merges A's history (element-wise max) before bumping its own slot, so B's vector now carries proof of everything it has heard from A, not just a single number.

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 slot b.

Work two real examples.

  • [2,1,0] versus [2,3,0]: first is <= second in every slot (2<=2, 1<=3, 0<=0) and slot b is 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.

Why Dynamo-style databases reach for vectors

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.

Cheap, total order
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.
Detects conflicts
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.
The phrase that shows you have read past the textbook

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.

Follow-ups the interviewer will ask (and your answers)
"Why not just run NTP everywhere and use wall-clock timestamps?"
NTP still leaves tens of milliseconds of skew and can jump backward on correction. Two events 5 ms apart on different hosts cannot be reliably ordered, so ordering by physical time can let the write that truly came second win and lose the newer data. I order by causality instead.
"Two Lamport timestamps are 3 and 5. Did the first cause the second?"
Not necessarily. The guarantee is one-directional: X -> Y implies LamportClock(X) < LamportClock(Y), but the reverse is false. Those two events may be concurrent on different machines. To actually detect that, you need vector clocks.
"Your vector clock comparison returns concurrent. What do you do?"
Detection is step one; resolution is a separate layer. Return both siblings to the client, apply a deterministic CRDT merge, use last-writer-wins with a documented tie-break, or escalate to the app. I avoid LWW for carts and balances because it drops one of two real writes by design.
"Vector clocks grow with actors. How do you keep that bounded?"
Key the vector by the handful of coordinating servers, not by millions of clients (server-keyed or dotted version vectors, as Riak did). Dynamo instead truncated oldest entries, which bounds size but can fabricate false concurrency. Both are size-versus-correctness trades you name explicitly.
"I want timestamps that look like real time but stay causally correct."
Hybrid logical clocks. An HLC pairs a physical timestamp with a logical counter, so values stay close to wall-clock time while still respecting happens-before. CockroachDB uses them. That is the modern answer when human-readable and causally correct both matter.

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.

Thanks. Check your inbox, your Logical Clocks: Lamport and Vector prep plan is on the way.