Prompt and use cases
Replicas communicate by delayed messages and cannot rely on wall-clock order. Explain how to reason about event causality, why a scalar Lamport timestamp gives a consistent ordering but not a complete causality test, and when a vector clock is worth its metadata cost. The core category is general: distributed-systems reasoning and explicit trade-offs, not a particular database or programming language.
What the interviewer evaluates
- Whether you define happened-before instead of treating timestamps as physical time.
- Whether you update a Lamport clock on local events, sends, and receives correctly.
- Whether you state the one-way guarantee:
a -> bimpliesL(a) < L(b), but the reverse is not guaranteed. - Whether you compare vectors component by component and identify concurrent events.
- Whether you discuss process membership, vector size, message overhead, and replica churn.
- Whether you connect the clock choice to a concrete need such as conflict resolution or trace analysis.
Clarifications before answering
- Is the goal a deterministic total order, causal detection, or a consistent snapshot?
- Are process identities fixed, or can replicas join, leave, or restart?
- Can messages be duplicated, delayed, or delivered out of order?
- Must timestamps survive storage and cross-region replication?
- Is bounded metadata more important than exact concurrency detection?
- What should happen when two writes are concurrent: merge, ask the user, or pick a winner?
30-second answer framework
“Define happened-before as local program order plus send-before-receive, closed transitively. A Lamport clock increments before each local or send event; on receive it sets max(local, received) + 1. This preserves causality, so a -> b implies L(a) < L(b), but a smaller scalar can also come from unrelated concurrent events. A vector clock stores one counter per process, increments its own entry, and merges by component-wise maximum on receive. V(a) < V(b) component-wise means causality; incomparable vectors mean concurrency. Use Lamport clocks for a compact deterministic order and vectors when distinguishing concurrent updates is required.”
Step-by-step deep answer
Step 1: Define the relation.
Write a -> b when a precedes b in one process, a is a send and b its receive, or a transitive chain connects them. Wall-clock readings are not part of this definition.
Step 2: Implement a Lamport clock.
onLocalOrSend:
clock = clock + 1
attach clock to an outgoing message when sending
onReceive(messageClock):
clock = max(clock, messageClock) + 1
process the messageFor a deterministic total order, compare (clock, processId). The process ID is a tie-breaker; it does not add causal information.
Step 3: State the guarantee and counterexample.
If a -> b, Lamport rules force L(a) < L(b). The converse fails: two independent processes can produce events with values 4 and 7 even though neither event influenced the other. A scalar cannot tell whether the gap represents causality or unrelated local work.
Step 4: Implement a vector clock.
onLocalOrSend:
vector[me] = vector[me] + 1
attach a copy of vector to the message
onReceive(remote):
for each process p:
vector[p] = max(vector[p], remote[p])
vector[me] = vector[me] + 1For vectors A and B, A <= B means every component of A is no larger than B; A < B additionally requires one strict component. A < B indicates A -> B. If neither vector is less than the other, the events are concurrent under the represented process set.
Step 5: Compare cost and membership.
Lamport metadata is one scalar plus an optional tie-breaker. Vector metadata is proportional to the tracked process set and grows in every message. Dynamic membership needs an epoch, sparse representation, dotted version vectors, or another explicit policy; silently reusing a process ID can merge unrelated histories.
Step 6: Choose a use case.
For a log viewer that only needs a repeatable order, Lamport timestamps plus a stable tie-breaker are often enough. For multi-writer replication, use vectors when concurrent writes need separate presentation or a domain merge. A vector clock does not resolve the conflict itself; it supplies evidence that the resolver must handle.
Step 7: Define failure and recovery behavior.
Persist the clock with the event or state it describes, restore it monotonically after restart, and decide how to treat messages from an old epoch. Test delayed, duplicated, reordered, and concurrent messages; physical clock synchronization does not replace these rules.
High-quality sample answer
“Happened-before is the partial order from local order, send-before-receive, and transitivity. Lamport clocks increment on local/send events and use max(local, received)+1 on receive. They guarantee a -> b implies L(a) < L(b), but equal or ordered scalar values cannot prove that two events are causally related. A vector clock increments the sender’s component and merges vectors by component-wise maximum before incrementing the receiver’s component. If one vector is strictly component-wise smaller, that event happened before the other; incomparable vectors are concurrent. I choose Lamport clocks for compact deterministic ordering, vectors for conflict detection, and I budget vector metadata plus a membership/epoch policy before claiming the design is complete.”
Common mistakes
- Sort by wall-clock time → clock skew and delay can invert causality → define happened-before explicitly.
- Claim
L(a) < L(b)provesa -> b→ scalar clocks only provide a one-way implication → give the concurrent counterexample. - Forget the receive increment → later local events can appear older than the message → apply
max + 1before processing. - Merge vectors by addition → counters represent knowledge, not quantities to sum → take component-wise maximum.
- Compare vectors lexicographically → lexicographic order hides concurrency → use component-wise comparison.
- Treat a vector clock as conflict resolution → it detects concurrency but cannot choose domain semantics → define a merge or user decision.
- Ignore membership and restart → reused IDs can conflate histories → use epochs or an explicit membership policy.
Follow-up questions and responses
Follow-up 1: Can Lamport clocks detect concurrency?
No. They can prove that one event precedes another when the scalar order is derived from a known causal path, but an ordered pair of scalar values may also belong to unrelated processes.
Follow-up 2: Why add a process ID to a Lamport timestamp?
The ID breaks ties to produce a deterministic total order. It does not improve causal knowledge and should not be presented as a vector-clock substitute.
Follow-up 3: What does an incomparable vector mean?
Neither event is known to have influenced the other within the tracked process set, so they are concurrent. The application still decides whether to merge, retain both, or reject one.
Follow-up 4: What happens when a message is duplicated?
The receiver takes component-wise maxima, so replaying the same vector does not reduce knowledge. The application may still need message IDs for idempotent side effects.
Follow-up 5: How do you bound vector metadata?
Track active members, use sparse or dotted representations, or weaken the guarantee with a documented approximation. A fixed bound that silently drops members can produce false concurrency or false ordering.
Follow-up 6: Do synchronized physical clocks make logical clocks unnecessary?
No. Synchronization has error bounds and failures; physical timestamps can help with display and retention, while logical clocks encode message-derived causality.
Follow-up 7: How would you test the implementation?
Generate traces with local, send, receive, delayed, duplicated, and concurrent events. Assert every known happened-before edge is ordered, every vector merge is monotonic, and intentionally concurrent pairs remain incomparable.