Approach 6: Logical Clocks
In one line: logical clocks sidestep the clock problem entirely — no synchronization, no drift, no NTP. The cost is size, and watching that cost collide with the 64-bit budget is the lesson.
Lamport clocks
Lamport clocks establish a partial ordering of events. The algorithm is small:
1. Each process keeps a counter, initialized to zero.
2. Increment the counter before every local event.
3. When sending a message, include the current counter as a timestamp.
4. On receiving a message, set the clock to:
max(local clock, received timestamp) + 1
before recording the receipt event.
This guarantees that if event a causally precedes event b, the timestamp of a is less than that of b.
Lamport clocks provide a partial ordering consistent with the happened-before relationship. A total order can be derived by breaking ties with node or process identifiers, though the resulting order depends on the identifier assignment you chose.
The limitation that matters
A total order consistent with causality but not capturing it — exactly enough for a sequencer, and not enough to detect concurrency.
Lamport clocks cannot fully capture causality across all events. Comparing two Lamport timestamps alone is insufficient to determine whether a happened-before relationship exists.
The asymmetry: if a caused b, then L(a) < L(b). But the converse does not hold — L(a) < L(b) does not mean a caused b. They might be entirely concurrent and just happen to have those counter values.
So a Lamport clock can tell you an ordering is possible, never that it is real. Vector clocks address this limitation by explicitly tracking causal dependencies.
Vector clocks
Vector clocks maintain causal history — they capture happened-before relationships between events. Each node tracks a counter per node, so the full dependency structure is visible.
The chapter's proposed ID layout:
1 bit 53 bits 10 bits [sign] [vector clock] [worker] 1 + 53 + 10 = 64 bits
| Field | Bits | Purpose |
|---|---|---|
| Sign bit | 1 | Always zero |
| Vector clock | 53 | The counters of each node |
| Worker number | 10 | 2^10 = 1,024 worker IDs |
The ID pattern is [vector-clock][worker-id]. Following three nodes A, B, and C in a data center:
Start: A = [0,0,0] B = [0,0,0] C = [0,0,0] Event A1 on node A: A = [1,0,0] -> ID: [1,0,0][A] Event C1 on node C: C = [0,0,1] -> ID: [0,0,1][C] Event B1 on node B, having seen A1: B = [1,1,0] -> ID: [1,1,0][B]
Read B = [1,1,0]: node B knows about one event from A and one of its own, and nothing from C. That single vector encodes B's entire causal history — which is exactly what Lamport's scalar counter could not do.
Why vector clocks don't fit
Vector clocks capture full causal history but require a vector size of n, where n is the number of nodes. In large systems — or systems where every client (a web browser, say) is a node — the storage and bandwidth requirements become prohibitive.
The 64-bit budget breaks immediately
53 bits for the whole vector means roughly 5 bits per node across 10 nodes, or fewer bits each as nodes grow. A counter with 5 bits wraps after 32 events.
And the vector must have one entry per node. Ten nodes might squeeze in; ten thousand cannot, and a system where every browser is a node is hopeless. That is why the scorecard marks vector clocks as "can exceed" on the 64-bit requirement rather than ✓ or ✗ — the size is a function of cluster size, so whether it fits is not a fixed answer.
This is the same unbounded-growth problem key-value stores solved with truncation — and truncation there cost reconciliation accuracy. Here it would cost the very causality you adopted vector clocks to get.
Scorecard
| Unique | Scalable | Available | 64-bit numeric ID | Causality maintained | |
|---|---|---|---|---|---|
| Using a range handler | |||||
| Using Twitter Snowflake | weak | ||||
| Using vector clocks | weak | can exceed |
The first approach to fully satisfy causality — and it pays for that with the size and scalability columns.
Key takeaway
Lamport clocks are compact and give only a partial order you cannot invert. Vector clocks give true causality and grow with the cluster. Neither fits a fixed 64-bit budget at scale — which sends us back to physical time, done properly.
Interview signal by level
| Level | What a strong answer sounds like |
|---|---|
| L4 | "Logical clocks use counters instead of real time." |
| L5 | Distinguishes them: "Lamport gives a partial order; vector clocks track a counter per node so you can actually detect concurrency." |
| Staff+ | Names the asymmetry and the size wall: "with Lamport, a-before-b implies L(a) < L(b) but not the converse — so it can't prove causality. Vector clocks can, but they're O(n) in cluster size, which blows a fixed 64-bit budget the moment n is large or every client is a node." |
Next: what happens if you buy better clocks.