Operational Transforms vs CRDTs
Why Google Docs needs a server and Figma doesn't: how two competing approaches to concurrent editing resolve the same-string conflict, and when each wins.
On this page
Google Docs can merge a keystroke from Tokyo and a deletion from São Paulo, both typed in the same millisecond against the same sentence, and produce the correct result. Figma does the same, but without a central server—each client merges changes offline, reconnects, and converges to the same canvas as every other client without asking anyone’s permission. Both systems solved the same mechanical problem: what do you do when two operations arrive out of order and the second one’s index is no longer valid? The answer separates wire-efficient collaborative platforms from peer-to-peer ones.
Two concurrent edits on the same string expose why position indices fail: the second operation was born against an old state and cannot be blindly replayed.
The Naive Approach: Why Position Indices Fail
Start with a string: HAT. Two users are editing it at the same time.
- User A opens
HATand insertsXat position 1, intendingHXAT. - User B opens the same
HATand deletes position 2 (theT), intendingHA.
Both operations are created against the same state. If User A’s edit reaches the server first, the canonical state becomes HXAT. Now User B’s operation arrives—still carrying its original payload: “delete position 2.”
If you naively apply it: position 2 in HXAT is A. You delete A, producing HXT. But User B wanted to delete T, not A. The result is wrong.
This is the index-shift problem. Operations carry positions that are only valid against the state their author saw. Every other replica that did not see the same state interprets that position differently. Replaying operations in receive order without adjustment produces corruption.
Three strategies exist to escape this trap. One has won in practice; the other is theoretically cleaner but heavier. Let’s explore both.
Two Solutions: Transformation vs. Identity
Operational Transforms (OT): Adjust the Index
OT’s insight: don’t replay the operation as-is. Transform it against all operations it did not see, adjusting its index so it targets the right element in the new state.
In the HAT example:
- User A’s op: insert
Xat position 1. - User B’s op: delete position 2.
- User B’s op is transformed: since A inserted 1 character before position 2, B’s position must shift up by 1.
- B becomes: delete position 3.
- Apply to
HXAT: deleteTat position 3 →HXA. Correct.
The catch: OT’s transformation functions are notoriously subtle. You must define transform(opA, opB) for every pair of operation types—insert vs. insert, insert vs. delete, delete vs. delete. Getting commutativity wrong (so that transform(A, B) + transform(B, A) do not converge) means clients diverge silently. Google spent years on their implementation; even then, bugs slipped through.
OT also requires a central server. The server is the authority: it receives all operations, orders them with a logical clock or total sequence number, transforms each incoming operation against the previous history, and broadcasts the canonical result. Clients cannot merge peer-to-peer because without a referee, you cannot agree on which transformations to apply.
CRDTs: Assign Immutable Identities
CRDTs flip the problem. Instead of relying on positions that shift, assign each element a unique, stable identifier when it is created. Operations reference IDs, not indices. Merging becomes simple: apply all operations in the order they were given, and they converge because the IDs are immutable.
Same HAT example with CRDTs:
- Initial characters:
H(ID:h₁),A(ID:a₁),T(ID:t₁). - User A’s op: insert
X(ID:x₁) beforea₁. - User B’s op: mark
t₁as deleted (tombstone). - Merge: apply both ops by ID → characters in order
h₁, x₁, a₁, t₁-deleted→HXA. Correct, no transformation needed.
The elegance: if User A and User B receive each other’s operations in different orders, they still converge. Both apply insert-X-before-A and mark-T-deleted. The IDs define the canonical order. Merging is commutative — the order of operations does not matter.
The cost: every character is now a record {value, id}, not just a character. Deletions are tombstones, not erasures, because you cannot remove data until you know every replica has seen the deletion (otherwise, if a straggling replica resurrects the deleted character after a merge, you diverge). A 1 MB document becomes 10+ MB in memory.
How Each Approach Works: Mechanism
OT’s Architecture
- Client A issues an operation locally and broadcasts it to the server:
insert("X", position=1). - Server receives A’s op, assigns it version number V1, applies it: state is now
HXAT. - Client B, still editing against the old state, issues:
delete(position=2)— and sends it to the server. - Server receives B’s op. B was created against V0 (original
HAT), but the server is now at V1. Transform B’s op against V1:delete(position=2)→delete(position=3)(because A inserted 1 char before position 2). - Server applies the transformed op:
HXAT→HXA. State is now V2. - Both clients download the canonical history (or receive diffs) and rebuild to V2:
HXA.
The server is essential: without it, clients cannot agree on the transformation order.
CRDT’s Architecture
- Client A (offline or online) creates an operation with its own node ID and logical clock:
insert(value="X", id=(nodeA, clock=1), before=a₁). - Client A applies it locally →
HXA(on client A’s replica). - Client B (offline or in a different session) creates:
delete(id=t₁). - Client B applies it locally →
HA(on client B’s replica). - Merge: when A and B sync (via server, P2P, or sneakernet), they exchange operations. Client A receives B’s
delete(t₁)and applies it by ID: the deleted character vanishes, leavingHXA. Client B receives A’sinsert(id=(nodeA,1), before=a₁)and applies it by ID: insertsXbeforea₁, producingHXA. Both converge.
No server, no transformation, no total order needed. Offline-first systems thrive here.
Both approaches converge to the same result HXA, but OT transforms indices while CRDT relies on immutable character identities.
Trade-offs: When to Choose Each
Choose OT if:
- You control a central server and can tolerate clients being online-first.
- Wire efficiency matters—OT operations are compact (delta + position), while CRDT tombstones bloat state.
- Your domain has simple operation types (insert/delete at a position). Complex merges (e.g., reordering a tree) are hard to transform correctly.
- You are Google Docs and you have already debugged the transformation functions over a decade.
Choose CRDT if:
- You need offline-first or peer-to-peer collaboration (Figma, local-first apps).
- You can afford the metadata overhead (character IDs, tombstones).
- You want proven, well-tested algorithms (like Yjs, Automerge) rather than custom transformation logic.
- Simplicity and correctness matter more than bandwidth. CRDTs converge by construction; OT correctness depends on subtle, easy-to-break transformation functions.
Neither is universally better. OT dominated the 2000s and 2010s because servers were normal and bandwidth was scarce. CRDTs are rising because offline-first and decentralized systems are becoming mainstream. Google Docs and Microsoft Word still rely on OT. Figma, Linear, and a new generation of local-first tools use CRDTs (via libraries like Yjs or Automerge).
The Real Lesson
The index-shift problem is unavoidable in concurrent editing. You must either (1) transform indices to account for unseen operations, which requires a referee to agree on order, or (2) abandon indices and use immutable IDs, which trades bandwidth for simplicity. There is no free lunch—only trade-offs, and the choice depends on your infrastructure and values.
Most engineers encounter this problem when they try to build multiplayer features into their app. Now you know the two canonical solutions and why each one exists. The next time you edit a Google Doc with a colleague or collaborate on a Figma design, the convergence you see is one of these two mechanisms working quietly in the background.
Operational Transforms require a central server but are wire-efficient; CRDTs are peer-to-peer but carry metadata overhead. Both solve the index-shift problem—one by transforming indices, the other by assigning immutable identities. Your infrastructure, not theory, should guide the choice.
Frequently asked questions
What is the index-shift problem in concurrent editing?
When two users concurrently edit a string at different positions, the operations carry indices valid only against the state each user saw. If you naively apply the second operation at its original index against the first user's modified state, the result is wrong—you delete or insert at the wrong position because the indices have shifted.
Why does Operational Transform require a central server?
OT needs a total ordering of operations across all clients to know which transformation to apply. A central server acts as the authority—it receives operations, orders them, transforms each incoming op against all prior ops it did not see, and broadcasts the canonical result. Peer-to-peer OT is theoretically possible but requires expensive consensus.
How do CRDTs avoid needing a server?
CRDTs assign each element a unique, immutable identifier when created, rather than relying on mutable positions. Operations reference these IDs instead of indices. When replicas merge, the IDs define a canonical order—no transformation needed, and no central referee. Each replica converges to the same state given the same set of operations, regardless of order.
What's the metadata cost of using CRDTs?
Every character or element needs a unique ID—typically a tuple of (node ID, logical clock) or a unique UUID. Deleted elements are tombstones marked as removed rather than erased from memory, because you cannot truly delete until you know all other nodes have seen the deletion. A string of 1 MB can balloon to 10+ MB in memory as metadata accumulates.
When is OT the better choice?
OT is better when you control the server infrastructure and prioritize wire efficiency. Google Docs chose OT because they run centralized services and wire bandwidth matters at scale. OT operations are compact—just the delta and position. Use it for closed platforms, internal collaboration tools, and systems where a central hub is acceptable. CRDTs shine when you need offline-first, peer-to-peer, or truly decentralized systems.
/* Comments */
Comments are offline right now — we reconnect automatically, nothing is lost.