Why Wall Clocks Fail Across Machines
Every physical clock drifts. Even with protocols like NTP nudging machines toward agreement, clocks on different servers can differ by milliseconds or more, and network delays are unpredictable and asymmetric. If two machines simply stamp their events with local wall-clock time, comparing those stamps to decide which event happened first is unreliable: a message can be timestamped as sent before it was actually received according to another machine's clock, or two unrelated events can appear to happen at exactly the same moment. Worse, wall-clock time cannot tell you anything about causality, meaning whether one event actually influenced another, versus the two events simply happening to occur around the same physical moment while being entirely unrelated. A distributed database replica needs to know, for example, whether an update it just received was a reaction to a value it had already stored, or whether it was written independently by someone who never saw that value. Solving this requires abandoning the idea of a single global clock entirely and instead tracking, for every process, its own view of how far every other process has progressed. That is precisely what a vector clock captures.
How a Vector Clock Is Built and Updated
In a system of N processes, each process keeps a vector, an array of N counters, one slot per process, all starting at zero. Whenever a process experiences a local event, such as performing a computation or writing to its own storage, it increments only its own slot in its vector. When a process sends a message to another process, it attaches a copy of its current vector to that message. When a process receives a message, it does two things: first it takes the element-wise maximum between its own vector and the vector attached to the incoming message, meaning it compares each slot position by position and keeps whichever value is larger, and then it increments its own slot by one. Consider three processes A, B, and C, each starting at zero zero zero. A performs a local event, so its vector becomes one zero zero. A then sends a message to B carrying that vector. B, whose vector was zero zero zero, takes the element-wise maximum with the incoming one zero zero, getting one zero zero, then increments its own slot, resulting in one one zero. Meanwhile C, unaware of any of this, might independently perform its own local event, moving its vector to zero zero one. B's vector now clearly reflects that it knows about one event from A and one event of its own, while C's vector reflects only its own independent history.
Reading Causality From Two Vectors
To determine whether one event happened-before another using their vector clocks, you compare the two vectors position by position, one slot at a time. The first event happened-before the second if every single slot in the first event's vector is less than or equal to the corresponding slot in the second event's vector, and additionally at least one of those slots is strictly smaller rather than exactly equal. In plain terms: the earlier event's counters must never exceed the later event's counters in any position, and there must be at least one position where the later event's counter has actually moved ahead. If that condition holds, the earlier event is said to causally precede the later one, meaning there is a chain of local events and message sends and receives connecting them. This comparison works precisely because every increment and every element-wise maximum operation only ever pushes counters upward, and a message recipient always absorbs everything the sender knew at the time of sending. So if event two's vector dominates event one's vector in this way, it proves that some information from event one could have flowed, directly or indirectly, into event two.
Detecting Concurrency, Something Scalar Clocks Cannot Do
A simple scalar logical clock, such as Lamport's original logical clock, assigns each event a single increasing number, and it guarantees that if one event causally precedes another, the earlier event's number is smaller. But the reverse is not reliable: two events can end up with different numbers, one smaller and one larger, even when neither one actually caused the other, they just happened independently and got compared by coincidence of numbering. A single number simply cannot encode enough information to distinguish genuine causal ordering from pure coincidence. Vector clocks fix this by preserving per-process history rather than collapsing everything into one counter. Two events are called concurrent when neither one's vector dominates the other's in the way described above, meaning neither vector is less than or equal to the other in every position. In our earlier example, B's vector of one one zero and C's vector of zero zero one are concurrent: B's vector has a larger value in the first slot, but C's vector has a larger value in the third slot, so neither vector wins in every position. This precisely and correctly signals that B's event and C's event happened independently, with no causal relationship, which is information a scalar timestamp could never reveal.
Real-World Uses: Databases and Version Control
This ability to detect concurrency, rather than just approximate ordering, is exactly what several influential real-world systems needed. Amazon's original Dynamo system, described in its well-known 2007 paper, used vector clocks to track the history of updates to each data item across replicas. When two replicas received conflicting writes that were concurrent according to their vector clocks, Dynamo did not attempt to silently pick a winner and potentially lose data; instead it surfaced both versions to the application, or to the end user in some cases, so the conflict could be resolved with real semantic knowledge, since a machine cannot always know which of two concurrent edits should take precedence. Many modern distributed databases and key-value stores use similar mechanisms, sometimes called version vectors, a close cousin of vector clocks specialized for tracking replica versions rather than individual events. Distributed version control systems like Git face an analogous problem when merging branches: they need to know whether one commit is a descendant of another, meaning strictly caused by it, or whether the two commits diverged independently and therefore need a genuine three-way merge. The underlying logic, tracking per-source progress rather than trusting a single global counter, is the same idea vector clocks formalized for distributed computing generally.
Frequently asked questions
What exactly does each slot in a vector clock represent?
Each slot corresponds to one process in the system and holds a counter of how many local events that specific process has performed, as far as the process holding the vector currently knows. A process's own slot is always the exact count of its own events, while the slots for other processes represent the most recent information it has learned about them through messages.
Why does the receiver take the maximum instead of just adding the vectors together?
Taking the element-wise maximum ensures each slot always reflects the greatest known progress of that process, without double-counting events the receiver may have already heard about through another path. Adding the vectors would incorrectly inflate counts whenever information about the same events arrived more than once.
Can vector clocks tell you exactly when an event happened in real time?
No. Vector clocks capture logical ordering and causal relationships, not physical wall-clock time. Two events can be causally related or concurrent regardless of how close together or far apart they occurred in real time; the vectors only tell you about the flow of information between processes.
Do vector clocks scale well to systems with many processes?
This is their main practical drawback: since each vector needs one slot per process, the size of every vector and every message attachment grows with the total number of processes in the system, which becomes costly at very large scale. Real systems often use techniques like pruning, grouping, or approximate version vectors to manage this overhead.
How is a vector clock different from a version vector used in databases?
They use the identical underlying comparison technique, but a version vector is typically attached to a single data item or replica to track which updates it reflects, rather than to every individual event in a whole distributed computation. In practice the terms are often used interchangeably when discussing conflict detection in replicated storage systems.
Try it live
Everything above runs in your browser — open Vector Clocks: Ordering Events Without a Shared Clock and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.
▶ Open Vector Clocks: Ordering Events Without a Shared Clock simulation