Causality in collaborative editors · Part 1 of 2
No Effect Before Its Cause: From Einstein to Yjs
How a 2,500-year-old idea, Einstein's moving train, Leslie Lamport's clocks and leaderless replication all converge on one rule: no effect before its cause.
· 15 min read
Before I embarrass myself
I’m writing this because I didn’t have anything better to do. Don’t judge me. I’m still interesting.
Most people spend a free weekend hiking or posting sunset photos captioned “grateful.” I spent mine with a laptop and a dog I’ve photographed roughly 400 times, doing absolutely nothing.
Also, full disclosure: I’m terrible at physics. If you’re a physicist, please be gentle.
And yet, I somehow decided to figure out how a collaborative text editor actually works. One thing led to another, and before I knew it, I was reading about Albert Einstein, lightning, and trains.
This is the story of that spiral.
Buckle up.
The sky is lying to you
Go outside tonight and look up. That Sun you saw earlier? You were looking at it as it was about 8 minutes ago. Proxima Centauri? That’s over four years old. The Andromeda galaxy is a 2.5-million-year-old photo, which makes it older than every embarrassing picture your friends have of you, combined.
The sky is not a live stream. It is a museum of the past, and every exhibit is from a different year.
Now open a shared Notion-style editor while your colleague edits the same paragraph from another continent. Congratulations, your screen is also a museum. What you see of their typing is a few milliseconds old, or a few hours old if their Wi-Fi decided to go spiritual and “disconnect for a while”.
Physicists spent a whole century learning to live without a universal clock. Software engineers had to learn the same lesson, except we cried more. Some collaborative editors handle it by going leaderless. There’s no boss server deciding who typed first, no single source of truth everyone has to bow to. Even when a sync server is in the middle, it’s just a messenger relaying updates, not a judge. Every replica accepts edits on its own, online or offline, and once they have all received the same updates, they end up with the exact same document. It’s like a group project with no team lead where somehow nobody’s work gets deleted. Hallelujah, a miracle! To understand how that’s even possible, we need to bully a train.
Einstein’s train: two people, one lightning storm, and the most pointless argument ever
Some time ago, Einstein showed that two events can be simultaneous for one person and not simultaneous for another, and both people are right. Imagine the arguments this would have settled at the regular drinking sessions at my previous workplace. After a few rounds, everyone turned into Aristotle, Diogenes or Nietzsche, and the debates got deep: the meaning of life, the existence of God, and, most importantly, who bought the last round first. According to Einstein, they were all right. By midnight, they always reached the same conclusion anyway: “One more round.”
Anyway, back to the issue at hand. A while later, Einstein explained it all with a train. Out of everything in the universe he could have picked to explain time, he picked the one thing that is never on time. Genius. Pure genius.
Warm-up: how normal physics works
I don’t know if this belongs here, but anyway, here it is. Last week at the office, during the tea break, we were sharing random, dumb facts about anacondas and octopuses, which is how every great scientific debate begins. Then someone asked a simple question: which takes longer, a flight from Katunayake in London to Katunayake in Colombo (this is not a mistake, if you know, you know), or the same flight in reverse, from Katunayake in Colombo to Katunayake in London?
Then the theories arrived. Most of us agreed one of them must be faster because of the Earth’s spin: the Earth turns under the plane, so one direction must get a free ride. We all agreed that one flight is faster than the other, and that was the end of the discussion. Nobody checked why. So let’s do it properly now, with a deep dive that starts with a train.
Before we mess with light, let’s see how normal things behave. This part is easy, and it has no drama. You already know it, even if you don’t know that you know it.
Imagine a train moving at 200 kmph (not a Sri Lankan train, obviously). You are sitting inside, and you throw a ball forward at 20 kmph.
What you see. The ball moves forward at 20 kmph. You are also moving at 200 kmph, but you feel nothing special. It’s the same as pouring tea on a moving bus: the tea doesn’t fly to the back, it just sits in the cup like normal.
Why nothing weird happens. You, the ball, the seats, the tea and the air inside the train are all moving at the same 200 kph together. When everything shares the same motion, it cancels out inside. Physicists call this bubble of things moving together a frame of reference. Inside the bubble, life looks exactly the same as standing still.
What a person outside sees. Someone standing next to the track sees the ball moving at the train’s speed plus your throw: 200 + 20 = 220 kph. The ball carries the train’s speed with it.
So it’s one ball with two correct speeds: 20 kph for you, and 220 kph for the person outside. Speed depends on who is watching, and nobody is wrong.
The rule is simple. With normal things, speeds just add up. Throw the ball forward and you add. Throw it backward at 20 kph and the person outside sees 200 − 20 = 180 kph.
And that finally settles the tea-break debate. The Earth’s spin doesn’t give either flight a free ride, because the air spins along with the Earth, just like the air inside the train moves with you. The real reason London to Colombo is usually faster is the jet stream, a band of fast wind high up that blows from west to east. Flying east, you get a tailwind. Flying back, you fight a headwind. Case closed, no extra rounds required.
Hold on to this rule, because light refuses to follow it. Shine a torch forward on the train and you would expect the person outside to measure the light going faster than normal. They don’t. Light stays at the same speed for everyone, and that is where all the trouble starts.
Here’s the setup. A train is zooming along a track. Lightning strikes both ends of the train. A woman is standing on the platform, exactly halfway between the two strikes. Both flashes reach her at the same moment, so she says: “Simultaneous. Obviously.”
Meanwhile, a man is sitting in the middle of the train, minding his own business, doing anything but drinking, not like that office buggers. While the light is travelling, the train moves him toward the front flash and away from the rear one. So the front flash reaches him first. Both flashes travel at the same speed, and he is sitting exactly in the middle of the train. So the flash that reaches him first must have happened first. He says: “The front strike happened first.”

Who’s right? Both of them. “At the same time” is not a fact about the universe. It’s a fact about whoever is watching. Einstein basically invented “it depends”, and computer scientists have been billing for it ever since.
Hold on to this, because it is exactly the problem with comparing timestamps from two servers. Every server’s clock is its own little train. Clocks drift, jump, get corrected by NTP at the worst possible moment, and disagree with each other like our friendly Bebadu friend from my office. Asking “whose edit came first?” is like asking the woman on the platform and the man on the train to agree on which strike came first. Each server’s clock is its own point of view, so they won’t agree. Stop asking.
Light cones: the VIP section of spacetime
So if nobody agrees on when, does anyone agree on anything? Yes: cause and effect. People can argue about timing all day, but an effect can never show up before its cause. The bouncer who enforces this rule is called the light cone.
Take any event, like a firework going off. Light spreads out from it in every direction, and nothing in the universe is faster than light. Not even your project manager asking “any updates?”. That growing bubble of light splits all of spacetime into three zones:
- The future. Stuff the firework can still reach. Like your inbox after a holiday: it’s coming for you.
- The past. Stuff that could have sent news to the firework in time. Like the friend who should have said, “Maybe don’t light it.”
- The elsewhere. Everything else. Too far away for any news to arrive in time, in either direction. Neither event can cause or affect the other. Their news might bump into each other later, somewhere else, but they can never be cause and effect. It’s tragic, really.

Here’s the rough rule. Pick any one observer and measure two things: how long light needs to travel between the two events’ locations, and how much time actually passed between them. If more time passed than light needs, a signal could have connected them, and every observer agrees on which came first. If less time passed, they’re in each other’s elsewhere: no signal could connect them, so neither can affect the other, and observers moving differently can disagree about their order. The best part: every observer agrees on which of the two cases it is, even when they disagree about the times.
Quick example: two events on opposite sides of the Earth are about 20,000 km apart, and light needs about 67 milliseconds to cross that. One second apart? They can be causally linked. Ten milliseconds apart? No connection possible. Asking which came first has no physical answer, just like asking one of my friends which of his two exes was “the problem”. This one is for you, buddy.
The elsewhere is where all the drama lives. Physics drama, software drama, all of it.
Lamport: “what if computers, but physics?”
After Einstein’s shenanigans came another bugger, Leslie Lamport. He looked at a network of computers and thought, “this is basically a tiny universe”. Honestly, good of him.
His paper, “Time, Clocks, and the Ordering of Events in a Distributed System”, draws a network as a simple picture. Each computer is a vertical line, with time moving down the line. When one computer sends a message to another, you draw an arrow from the sender’s line to the receiver’s line. The arrow slants because the message takes time to travel: it leaves at one moment and lands at a later one. Messages are the light rays of this universe. They are the only way one machine can find out what another machine did. Machines do not have gossip. They have packets.

Read it by following the arrows. Even though c1 sits between a1 and a2 on the page, no chain of arrows connects them, so they are concurrent, whatever any clock says.
From that picture, Lamport defined happens-before, and notice it never mentions a clock. Not even once. The man was done with watches. Event A happens before event B if:
- they’re on the same machine and A came first, or
- A is sending a message and B is receiving it, or
- there’s a chain of those steps from A to B.
If neither happens before the other, the events are concurrent. They’re in each other’s elsewhere. Martin Kleppmann sums it up nicely in Designing Data-Intensive Applications: operations count as concurrent when neither knows about the other, no matter what the clock said. Concurrency isn’t about doing things at the same time. It’s about being clueless at the same time. Relatable.
Where the analogy breaks: the internet has no speed limit, just vibes
In physics, the speed of light draws a clean, fixed line around what can affect what. Networks have no such line. What matters isn’t whether information could have arrived. It’s whether it actually did.
Light could zip between any two data centres on Earth in well under a tenth of a second. Yet two edits made an hour apart can still be concurrent, because the message never got there. A dropped connection, a crashed server, or a laptop that went to sleep mid-sentence stretches the elsewhere from milliseconds to hours. A network’s light cone is ragged, unpredictable, and emotionally unavailable.
Meet Alice and Bob
Every distributed systems article is legally required to feature Alice and Bob. I don’t make the rules.
But before they appear, a quick credit. This whole rabbit hole started with the work of Tharaka Ariyarathna, an absolute legend and genius. His Notion-like collaborative editing tool for a CSM platform is what pulled me in. Without his work, I would not be talking about any of this. I would be taking 400 more photos of my dog.
If you want to say hi to him, or you’d like to work with him, you can find Tharaka on LinkedIn.
So, Alice and Bob are editing the same sentence: “The car is “. Alice types “fast” at the end. Bob is on a train (yes, another train, the universe is a train enthusiast). Just as the train enters a tunnel, his internet disconnects. Bob doesn’t notice, so he keeps typing and adds “slow” in the same spot while he is offline. A few minutes later, the train comes out of the tunnel and he reconnects.
By the wall clock, Bob typed minutes after Alice. But wall-clock order is not cause-and-effect order, and only the second one matters here. The key fact is this: Bob never saw “fast”. His edit lives in Alice’s elsewhere. So the two edits are concurrent, and any system that uses timestamps to decide will get it wrong, and then blame the user.
Here’s the whole drama, drawn the way Lamport would draw it. Each vertical line is a replica moving through time, and each arrow is a message, the network’s sad little light rays.

No chain of arrows connects Alice’s edit to Bob’s, in either direction. They’re strangers in the elsewhere. The minutes between them on the wall clock say nothing about cause and effect, like the “5 min away” on a food delivery app.
This is the big headache of leaderless replication. When any replica can accept edits at any time, with no boss server deciding the order, you will get situations like Alice and Bob, and you have to decide what to do with them.
Leaderless systems have several ways to deal with this. I will look at two of them, because both stay on the relativity road: they run on cause and effect, not on clocks.
The first is the Dynamo style, used by databases like Riak. It uses version vectors to notice that two writes are concurrent, then keeps both versions and lets the application merge them.
The most famous example of the Dynamo style is Amazon’s shopping cart. In the Dynamo paper, Amazon wanted “add to cart” to always work, even when the network was having a bad day, so any replica could accept a write. That means two replicas can end up with different carts. One has a book. Another, which never heard about the book, has a phone. Dynamo used version vectors (it called them vector clocks) to notice that the two carts were concurrent, and it kept both. When the customer next opened the cart, the application merged them by taking the union: now the cart has the book and the phone. Nothing you added is ever lost. The price is that a deleted item can sometimes come back from the dead, which explains the shoes you removed three times.

Read it top to bottom: the database never picks a winner. It keeps both carts as siblings, and the next write merges them.
Now the second way, the CRDT. It is built so that replicas that have seen the same updates always merge to the same result, with nobody stepping in to decide. No siblings, and no merge code for you to write.
I will walk through the CRDT way, with Yjs, because it is the one I have actual experience with, and I only have that experience because of Tharaka’s work. That’s the next article: Part 2: how Yjs pulls this off with a handful of bytes per character
Confused by the word in the title, Pratītyasamutpāda?
Fair question. It is a mouthful (roughly: pruh-TEE-tyuh-sum-oot-PAH-duh). It is a Buddhist idea usually translated as dependent origination: nothing arises completely on its own; things arise because the conditions for them already exist.
I’m not a Buddhist scholar, just like I’m not a physicist, so I’m borrowing the idea rather loosely. Please don’t send either profession after me.
But once you see the pattern, it shows up everywhere in this article. Einstein showed that observers can disagree about when something happened, but never about cause and effect. Lamport took that idea into distributed systems: forget agreeing on a universal clock; what matters is whether one event could have caused another.
That is exactly why Alice’s and Bob’s edits are concurrent: neither caused the other, so neither has to come first. But when one update does depend on another, that dependency matters. If an update arrives before the update it depends on, Yjs doesn’t freak out and declare the universe broken. It waits.
You never see a glass shatter before it falls, and an event in a distributed system can’t show up before its own cause just because the clocks are confused. Some things must come before other things, not because everyone agrees on the time, but because they are connected by cause and effect. In a strange way, that is the whole idea.
So, what did I learn with my free weekend?
Here’s the short version.
Einstein took away the universal “now” and gave us something better: an order of cause and effect that everyone agrees on.
Lamport took that idea and pointed it at computers. Instead of asking “what time did this happen?”, he asked “what caused this?” One event comes before another only if a chain of causes connects them: earlier steps on the same machine, or messages sent from one machine to another. No chain, no order, whatever the clocks say.
That is very close to the idea taught by Lord Buddha, the one in the title: nothing arises on its own, every event arises from the conditions that came before it. Lamport simply drew those conditions as arrows.
Then Kevin Jahns, the creator of Yjs, turned it into something you can npm install. Not bad for four guys who probably never met—although one of them had a 2,500-year head start. (Concurrent, if you will.)
To be continued: how Yjs actually pulls this off, with origins, YATA and state vectors. Read Part 2: Yjs Internals →
Further reading (if you have nothing to do)
- Albert Einstein, Relativity: The Special and the General Theory (1916), the source of the train thought experiment.
- Leslie Lamport, “Time, Clocks, and the Ordering of Events in a Distributed System” (1978).
- Martin Kleppmann, Designing Data-Intensive Applications, Chapter 5, “Detecting Concurrent Writes”.
- Nicolaescu, Jahns, Derntl and Klamma, “Near Real-Time Peer-to-Peer Shared Editing on Extensible Data Types” (GROUP 2016), the YATA paper.
- Yjs documentation and the Yjs source on GitHub.
- The Paṭiccasamuppāda Vibhaṅga Sutta (SN 12.2), the Buddha’s own explanation of dependent origination. Paṭiccasamuppāda is the Pali form of Pratītyasamutpāda.
Also published on Medium.
- distributed-systems
- crdt
- physics
- philosophy
Discussion
Loading comments…