JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Time in a Distributed System: Lamport Clocks

In one machine, a single hardware clock can stamp every event so you always know what happened first. Spread that machine across a network and the shared clock vanishes — and "what happened first?" becomes a surprisingly deep question. This guide shows why wall-clock time can never be fully trusted across machines, and how Lamport's logical clocks recover just enough ordering to keep a distributed system sane.

Why you cannot just look at a clock

On a single machine, ordering events is trivial: there is one CPU clock ticking away, and the kernel can stamp every event with a number from it. When two writes hit the same file, the OS knows which was first because it watched them go by on the same timeline. The first guide in this rung warned that a distributed system gives up exactly this comfort — there is no shared memory and no shared clock. Every machine has its own quartz crystal ticking at its own slightly-wrong rate. Ask two machines "what time is it?" and you will get two different answers.

It gets worse than "different by a bit." Crystals drift with temperature, so the gap between two machines' clocks grows over time. The protocol you met for fighting this, clock synchronization (NTP being the everyday example), nudges every machine toward a reference time — but it can only ever get close, never exact. The very message that carries the correct time spends an unpredictable amount of time crossing the network, so the answer is already stale when it arrives. After syncing, two machines might still disagree by a few milliseconds, and that is enough to ruin you: on a fast network a great many events can happen in a millisecond.

What we actually need: who-before-who, not what-time

Here is the liberating insight Leslie Lamport had in 1978. Most of the time we do not actually need to know the real time an event happened. We need to know its order relative to other events — which write won, which request came first, whether the reply could possibly depend on the question. And causality only flows through two channels in a distributed system: events on the same machine happen in a definite local order, and a message must be sent before it can be received. Everything else is genuinely unordered, and pretending otherwise is the mistake.

Lamport captured this with a relation he named happened-before, written with an arrow. We say event A happened-before event B (A -> B) if any of these hold: A and B are on the same process and A came first in that process's local sequence; or A is the sending of a message and B is the receiving of that same message; or there is a chain — A -> C and C -> B — so the relation passes through intermediate events (it is transitive). If neither A -> B nor B -> A holds, the two events are concurrent: nothing could have let one influence the other, so the system is free to treat them in any order.

Notice the careful word choice: happened-before is about potential causality, not real time. A -> B means A could have influenced B, not that it did. And concurrent does NOT mean "at literally the same instant" — it means "causally unrelated, so order does not matter." This mirrors a distinction from much earlier in your climb: message passing between processes is how independent actors coordinate without sharing state, and here it is the only thread that stitches two machines' timelines together.

The Lamport clock: a counter, two tiny rules

A Lamport clock is shockingly cheap: each process keeps a single integer counter, starting at 0. There is no hardware, no synchronization, no network chatter to maintain it. The counter is not a time of day — it is just a number that grows, designed so that if A -> B then the number stamped on A is smaller than the number stamped on B. Two rules govern it, and that is the entire mechanism.

  1. Before a process does anything it considers an event (a local step, sending a message, or receiving one), it increments its own counter by 1.
  2. When a process SENDS a message, it attaches its current counter value to the message as a timestamp, riding along with the data.
  3. When a process RECEIVES a message carrying timestamp t, it sets its own counter to max(local counter, t) and then adds 1 — taking the larger of the two and stepping past it.

That "max then plus one" on receive is the clever pivot. It forces the receiver's clock to leap ahead of the sender's send-event, so the receive is guaranteed to carry a larger number than the send. Combined with the local increment, this is exactly enough to make the timestamps respect happened-before: walk any causal chain and the numbers strictly climb. Let us watch it run.

Three processes. Each event shows its Lamport timestamp.
Time flows left to right; arrows are messages.

P1:  a(1) ---- b(2) -------------
                  \ msg ts=2
                   v
P2:  ----------- c(3) ---- d(4) --
                              \ msg ts=4
                               v
P3:  e(1) ------------------- f(5)

Why these numbers:
  a,b : P1 ticks 1,2 locally
  c   : P2 receives ts=2  -> max(0,2)+1 = 3
  d   : P2 ticks locally  -> 4
  e   : P3 ticks locally  -> 1
  f   : P3 receives ts=4  -> max(1,4)+1 = 5

Check: b->c (a message) gives 2 < 3.  Good.
       d->f (a message) gives 4 < 5.  Good.
       a(1) and e(1) are CONCURRENT: equal numbers,
       no causal path either way.
A 3-process trace. Notice the receive rule pushing P2's clock from a local 0 up to 3, and that the two events both stamped 1 (a and e) are genuinely concurrent.

What it gives you — and the honest catch

The guarantee Lamport clocks give is a one-way street, and stating it precisely matters: if A -> B, then timestamp(A) < timestamp(B). Always. That is genuinely useful — it means a smaller-or-equal timestamp can never come from an event that was actually caused by a larger one, so you can sort all events into a single consistent line (breaking ties by process id) without ever putting an effect before its cause. This is exactly the raw material protocols like distributed mutual exclusion need: to decide who enters a critical section first, every requester broadcasts a timestamped request, and the lowest timestamp wins. No central lock, no shared clock — just the counter.

But be honest about the catch, because it trips everyone the first time. The implication runs only one way. From A -> B you may conclude timestamp(A) < timestamp(B); you may NOT run the arrow backwards. Seeing timestamp(A) < timestamp(B) does not tell you that A happened-before B — they might be completely concurrent and just happen to have landed on those numbers (look again at how P3's event e got a 1 while P1's a also got a 1, or imagine two unrelated events numbered 3 and 5 on different machines). A Lamport clock can certify causality but it cannot detect the absence of causality.

Where this fits, and where it is going

Step back and see the shape of the idea. On one machine, ordering events is free because the hardware hands you a shared timeline — the same reason a single-machine memory consistency model can talk about which write a read sees. Take the shared timeline away and you must manufacture an ordering, out of the only causal hints the network gives you: local sequence and message send-before-receive. Lamport clocks are the minimal machinery that turns those hints into consistent numbers. It is a beautiful example of the distributed-systems mindset — stop chasing the global truth you cannot have, and engineer the weaker-but-achievable property you actually need.

This ordering machinery is foundational for the harder problems still ahead. The final guide in this rung tackles consensus — getting a flock of machines to agree on a single value or a single order of operations even when some of them crash or messages are lost. Algorithms that build a replicated, agreed-upon log (think replicated state machines behind replication) lean on exactly this notion of a consistent order of events. Lamport clocks do not solve consensus by themselves, but they hand you the vocabulary — happened-before, concurrent, a tie-broken total order — in which consensus is even expressible.