← Notes

Notes · July 2026 · Real-time, data structures

no one has to agree.

Two people type in the same document at once. Every keystroke has to land, and both people have to end up looking at the same text, without a lock and without silently dropping an edit. That sounds simple. The naive version breaks immediately.

The naive merge

Represent each edit as an operation: insert a character at position 5. Two people, editing concurrently, each compute that position against the same starting text. Apply both operations in arrival order with no adjustment, and the second one lands wrong. An edit arrived in between, and every index after it shifted. The result isn’t a merge. It’s corruption that happens to look like text.

Fixing this without a central lock is the actual problem. Two answers exist, and they solve it in opposite ways.

Operational transform

OT keeps positions and fixes the math. Before applying an operation from another replica — another person’s copy of the document — transform it against every operation it didn’t see: if a concurrent insert landed earlier in the text, shift this operation’s position past it. Get the transform function right for every pair of operation types, and every replica ends up with the same document regardless of arrival order.

Getting it right is the hard part, and it gets harder as the document gets richer. Plain-text insert-against-insert is manageable. Insert against delete, delete against delete, and then formatting, embedded objects, and nested structure each add their own cases the transform function has to handle correctly, forever. And “every operation it didn’t see” needs a definition — in practice, a central server that sequences every edit so everyone transforms against the same order.

CRDTs

CRDTs skip the transform function by skipping positions-as-indices entirely. Instead of “insert at index 5,” every character gets a permanent position value when it’s typed, chosen to sit strictly between its left and right neighbors. Insert a new character between two existing ones, and there’s always room to place it, without renumbering anything else.

That’s what makes the operations commutative: compare two characters’ positions and you get the same answer no matter what order either replica received them in. No transform function, no server to sequence anything — any two replicas that have seen the same set of operations converge on the same document, in any order, even fully out of order.

Feel it

The two panes below are independent replicas, not a shared textbox. Each keystroke becomes a real operation, sent to the other pane with a short random delay. Type in one, then the other: they converge once the delay clears. Type in both at the same spot at once, and watch them comb together instead — still convergent, just not always legible. That’s a real property of this scheme, not a bug in the demo.

The tradeoff

Convergence without coordination is paid for twice. Deleted characters can’t just disappear — if a delete arrives before the replica has seen the matching insert, there’d be nothing to mark, so deleted characters stick around as tombstones, and a long-lived document accumulates them forever unless something later compacts the history. And a scheme that only compares positions, with no memory of which characters arrived together, can interleave two people’s concurrent edits at the same spot, the way the demo just did — convergent, but not always readable. Production libraries like Yjs and Automerge track more of each operation’s context specifically to avoid that; this demo doesn’t, so you can watch it happen instead.

In practice, the choice often comes down to what else is already in the path. If nothing else needs a central server, CRDTs avoid adding one just for sequencing. If a server’s already there for auth or persistence, OT’s transform cost can be cheaper than moving coordination out to the clients entirely.

The principle

Coordination has to be paid for somewhere. OT pays with a server and a transform function that only gets harder as the document does. CRDTs pay with metadata that never shrinks, and sometimes with legibility during the merge itself. Neither one avoids the cost — they just move it to a different part of the system.