CodeCollab

Watch Operational Transformation resolve concurrent edits — two editors, one document, zero conflicts.

Both editors share a live document. Changes from either editor are merged in real time using a simplified OT algorithm — watch the log below to see how operations are transformed.

A
Alice Ready
B
Bob Ready

Converged Document (what both editors will see after sync)

Operation Log

Waiting for edits...

Choose a scenario to see how concurrent conflicting operations are detected and transformed.

Operational Transformation (OT)

OT transforms concurrent operations against each other so they compose correctly. If Alice inserts at position 3 while Bob deletes at position 5, and Bob's op arrives after Alice's, Bob's delete must be shifted to position 6 to account for Alice's insertion.

CRDTs

Conflict-free Replicated Data Types encode position into the data structure itself (e.g. Logoot, LSEQ, Yjs). Each character gets a globally unique fractional position — there's nothing to transform. CRDTs are simpler to distribute but use more memory.

The CP1 / CP2 Properties

CP1 (convergence): all replicas reach the same state. CP2 (intention preservation): the effect of each operation reflects the user's intent even after transformation. Achieving both simultaneously is the hard problem in OT.

Vector Clocks

Each client tracks a vector of operation counts per peer, e.g. [Alice:3, Bob:2]. This establishes a partial order of operations — detecting which were truly concurrent (neither happened-before the other) vs. sequential.

OT vs CRDT Trade-offs

OT: requires a central server (or peer coordination) to order concurrent ops; mature (Google Docs uses it). CRDTs: fully peer-to-peer, works offline, but character IDs can bloat with many edits. Yjs (used by VS Code Live Share) is CRDT-based.

Tombstoning

Deleted characters are marked as "tombstoned" rather than removed immediately. This preserves position indices for in-flight operations that reference those positions. Tombstones are garbage-collected once all peers have acknowledged the deletion.

OT Insert-Insert Transformation

State: "hello world"
Alice: Insert("!", pos=11) // append exclamation
Bob: Insert(" big", pos=5) // insert before "world"
Server receives Alice first:
Apply: "hello world!"
Bob's op arrives: Insert(" big", pos=5)
Transform: Alice inserted at 11, Bob inserts at 5 < 11 → no shift needed
Apply: "hello big world!" ✓ Both intentions preserved
State: "hello world"
Alice: Insert(" beautiful", pos=5)
Bob: Insert("!", pos=11)
Server receives Alice first:
Apply: "hello beautiful world"
Bob's op arrives: Insert("!", pos=11)
Transform: Alice inserted 10 chars at pos 5 < 11 → shift Bob's pos by +10 → pos=21
Apply: "hello beautiful world!" ✓ Both intentions preserved