Order known dependencies with logical counters
Construct happened-before order and Lamport updates without interpreting counter differences as durations or reversed implications as causality.
On this page
The question for today
What you’ll understand
- Build happened-before from local order, message edges, and transitivity.
- Apply increment and max-plus-one clock updates consistently.
- Separate a causality-preserving total order from evidence of a causal relationship.
A service writes a record and sends another service a message about it. Even when their wall clocks disagree, the receiving event cannot precede the corresponding send in the execution's dependency order. Logical clocks preserve this kind of relationship using counters rather than calibrated seconds. Their power comes from a deliberately narrow promise: known causal precedence makes the assigned value increase. Their limitation is equally useful: increasing values alone do not reveal which events actually influenced one another.
Define the relation before assigning timestamps
Model each participant as a sequence of recorded events. Earlier events on one participant precede its later events; sending a particular message precedes receiving that same message; and these relationships compose transitively. The resulting happened-before relation is a strict partial order. Events with neither direction related are concurrent in this model. That term describes missing causal order, not necessarily simultaneous physical execution. Two independent machines can perform events hours apart and still have no modeled message path connecting them. The answer also depends on where instrumentation defines the send, receive, and local events.
A participant is an ordered execution context, not automatically every thread sharing one process identifier. If a program has several concurrent threads, the local-order edges must reflect the actual synchronization model rather than an invented sequence from merged logs. An external dependency omitted from the model can also hide a real influence. State which communication channels are represented. A user copying a result between applications can create a meaningful real-world dependency that a narrow message-only trace misses. Logical time correctly represents the specified event model; it cannot manufacture knowledge of events that were never propagated or recorded.
Implement the clock condition
Maintain a nonnegative logical counter at each participant. For this lesson's convention, increment before every local or send event, and attach that event's counter to the message. For a receive event carrying remote value m, replace the local counter by one more than the maximum of its previous value and m. The exact bookkeeping convention may vary, but both local sequence and send-to-receive edges must increase strictly. Consistently applying one convention makes examples auditable; mixing an increment-before convention with an increment-after diagram can produce misleading off-by-one claims.
The clock condition says that happened-before implies a smaller timestamp. Its converse does not hold. An independent participant might have executed more local events and therefore have a larger counter without learning anything about the other participant. A gap of one hundred logical units is not a hundred milliseconds, messages, or missing log entries in the global execution. It can partly reflect jumping forward after receiving a large value. Keep counter state durable or introduce a well-defined incarnation when required across restart, and prevent overflow from silently turning a later event into a smaller ordinary integer.
A deterministic tie-break adds order, not knowledge
Two concurrent events can receive the same logical value. Adding a stable participant identifier supplies a deterministic lexicographic tie-break, producing a total order that extends happened-before. This can be useful for a protocol's arbitration rule, but it does not reveal the true physical order of concurrent events. Different legitimate identifiers or counter histories can produce different extensions. Nor does attaching timestamps alone implement reliable total-order delivery: a receiver still needs a protocol for learning when no lower-ordered event remains in flight. Representation, delivery, and application conflict resolution are separate design choices.
The modular lab demonstrates a representation hazard rather than a distributed clock. Reset, set both modulus m=8 and additive step a=1, predict what follows residue seven, then use Play or Step. Playback selects successive additions and eventually wraps to zero. Compare a fresh run only after another Reset and re-setting both controls. Ordinary numerical comparison would now place the later residue before an earlier one. Real Lamport implementations need sufficient counter space or an explicit rollover and incarnation scheme; the lab provides no messages, receive updates, or correctness protocol for handling that rollover.
Work it through
A message makes the receiver jump
A and B start with counters zero. A performs one local event and then sends a message. B has already performed four independent local events before receiving that message. Use the increment-before-event convention.
A’s local event receives value 1; its send receives value 2, which is carried by the message. B’s four local events leave its counter at 4.
B’s receive updates its counter to max(4, 2)+1=5. The actual send-to-receive edge has timestamps 2 and 5, preserving the clock condition.
B’s earlier local event with value 4 still has no specified happened-before relation with A’s send at value 2. The inequality 2<4 cannot supply the missing communication edge.
The receive is stamped 5, but larger counter values at B do not retrospectively establish dependencies for its earlier independent work.
The living notebook
Explore the model
Interactive lab · modular
Watch an ordinary integer order fail at wraparound
Reset, set both modulus m=8 and additive step a=1, predict the transition after residue 7, then Play or Step. Playback uses n=floor(2t); each Step advances one addition. This additive modular orbit is not a Lamport clock or a receive-message simulation.
- Residue r0
- 0
- Orbit period · m/gcd(m,a)
- 12
- Distinct residues visited
- 1 of 12
Addition modulo m starts at zero: rₙ = na mod m. Playback advances two additions per second. The orbit has period m/gcd(m,a), including period one for a = 0. This is not a cryptographic security test or a primality algorithm. Parameter changes pause playback and recompute the model at the current elapsed time. Reset restores both parameters and returns time to zero. Playback starts paused. Adjust either parameter, then choose Play or Step.
Look forA later wrapped residue can be numerically smaller. A distributed timestamp representation needs an explicit overflow policy rather than assuming modular values preserve ordinary increasing order.
Pause & reason
Check your understanding
Your turn
Try it yourself
A receiver has local counter 9 and accepts a message stamped 14. What receive value follows? Can that new value be converted to a physical deadline?
Take a moment to make a prediction or work it out on paper. Open a hint when you need one.
Give me a hint
Apply max-plus-one, then distinguish an event-order counter from an elapsed-time clock.
Compare with the solution
The receive value is 15. It establishes an ordering after the message’s send under the model, but has no calibrated duration unit and cannot be interpreted as a physical deadline without an additional clock contract.
Keep the idea
Take it with you
- Happened-before is a partial order built from defined event edges.
- Lamport values preserve causal order but do not characterize it in both directions.
- Tie-breaks and rollover policies add protocol decisions, not physical-time knowledge.
Read further
Original sources and deeper explanations to follow at your own pace.
Optional progress, saved in this browser when available. Revisit any lesson at any time.
Lesson not marked complete.