Crux Read clock-update code and event traces — Lamport increment, vector-clock merge, happens-before comparison, and concurrency detection — and predict the result.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Logical clocks are tiny pieces of code with subtle correctness rules. Read each update routine and event trace, then pick the answer a senior engineer would defend in review — where the bug, the order, or the concurrency verdict actually lives.
Goal
Practise the core operations of logical clocks in code: a Lamport increment on send and receive, a vector-clock merge, the happens-before comparison, and detecting that two events are concurrent rather than ordered.
The receive handler ignores msg_t. What invariant does this break, and what is the fix?
Heads-up Plain increment ignores the sender's progress. If the sender's clock was far ahead, the receive event can end up with a SMALLER timestamp than the send, violating that send happens-before receive.
Heads-up Resetting to msg_t can move your clock backward and drops your own local progress. The rule is max of the two plus one — never just adopt the incoming value.
Heads-up The receive IS an event and must advance the clock. The defect is dropping msg_t, not the increment; the fix takes the max first, then increments.
Snippet 2 — the vector-clock merge
def merge_receive(local, msg_vec, my_id): # local and msg_vec are dicts: node_id -> counter merged = {} for node in set(local) | set(msg_vec): merged[node] = max(local.get(node, 0), msg_vec.get(node, 0)) merged[my_id] += 1 return merged
Quiz
Completed
This receive merge is correct. Which statement captures why the elementwise max plus a single self-increment is exactly right?
Heads-up Incrementing every component would falsely claim this node observed new events on every other node. Only my own counter advances on a local/receive event; the rest are merged via max.
Heads-up Summing double-counts events both sides already share and breaks the comparison semantics. The max keeps each component at the latest count each party has actually seen.
Heads-up The receiver must never advance the sender's counter — only the sender owns it. The receiver bumps its own component and maxes the rest.
Snippet 3 — comparing two vector clocks
def compare(a, b): # returns 'before', 'after', or 'concurrent' le = all(a.get(k, 0) <= b.get(k, 0) for k in set(a) | set(b)) ge = all(a.get(k, 0) >= b.get(k, 0) for k in set(a) | set(b)) if le and not ge: return 'before' # a happens-before b if ge and not le: return 'after' if le and ge: return 'equal' return 'concurrent'
What does compare(A, B) return for these two vectors, and what does it mean for conflict resolution?
Heads-up Vector clocks are compared componentwise, never by summing. A leads on n1 and B leads on n3, so neither is uniformly less-or-equal — that is the definition of concurrent.
Heads-up A single larger component does not make A 'after'; you need EVERY component of A to be greater-or-equal. Here B beats A on n3, so they are concurrent, not ordered.
Heads-up Equal requires identical counts on every node. These differ on n1 and n3 in opposite directions, which is precisely the concurrent case the merge function must surface.
Using happens-before over this trace, what is the relationship between e on P1 and d on P3?
Heads-up Lamport timestamps give a total order, but a smaller-or-larger Lamport value does NOT imply causality. There is no message path from e to d, so they are concurrent regardless of the numbers.
Heads-up P3 receives m2; it does not start the chain (P1's a does), and no message flows from d back to P1's e. Without a connecting message path, the two events are concurrent.
Heads-up e and d are distinct events on different processes. Happens-before asks whether a message path connects them; here none does, so they are concurrent.
Recap
The clock operations are small but unforgiving: a Lamport receive must take max(local, msg) + 1 or it can stamp a receive earlier than its send; a vector-clock receive takes the elementwise max and bumps only its own component; comparing vectors is componentwise, and when each side leads on some node the events are concurrent and must be kept as siblings. And no Lamport number ordering implies causality without a connecting message path. Read the code and the trace, then decide order vs concurrency from the causal structure, not the raw integers.