Two users edit the same document simultaneously. User A inserts “X” at position 5. User B deletes the character at position 3. Apply both naively and the result is corrupted. The positions shifted when B’s deletion ran first, and A’s insertion lands in the wrong place.

The Position Problem#

Operations encode positions at generation time, not application time. When document state changes between generation and application, positions are stale. Operational Transformation (OT) transforms an incoming op relative to already-applied ops before executing it.

Two properties are required. Convergence: every client reaches the same final state regardless of operation order. Intention preservation: A wanted to insert after the 5th character, not wherever position 5 lands after B’s edit. Without both, you either get diverged replicas or semantically wrong merges.

Server as Coordinator#

OT requires a central server. Clients send ops, the server maintains a global op log, transforms each incoming op against concurrent already-applied ops, then broadcasts the transformed op to all clients. This is not an implementation detail. The server is part of the correctness model. Without a single serialization point, you cannot deterministically resolve which ops are “concurrent” and which are “already applied.”

Complexity grows fast. Plain linear text is manageable. Rich formats (tables, images, nested lists) multiply transformation functions combinatorially. Each new document element type needs transformation rules for every other element type. This is one reason some newer systems use CRDTs instead.

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#000000','primaryTextColor':'#00ff00','primaryBorderColor':'#00ff00','lineColor':'#00ff00','secondaryColor':'#000000','tertiaryColor':'#000000','noteBkgColor':'#000000','noteBorderColor':'#00ff00','noteTextColor':'#00ff00'}}}%% sequenceDiagram autonumber participant CA as Client A participant S as Server participant CB as Client B CA->>S: Insert("X", pos=5) CB->>S: Delete(pos=3) S->>S: Apply Insert("X", pos=5), record in op log S->>S: Receive Delete(pos=3), transform to Delete(pos=4) S->>CB: Broadcast Insert("X", pos=5) S->>CA: Broadcast Delete(pos=4) [transformed] Note over CA,CB: Both clients converge to identical state

At Salesforce, two users edited the same form field simultaneously. Last-write-wins silently overwrote one change. A support rep and a sales rep both edited “Account Name” on the same record. Whoever saved last won, the other disappeared with no notification. About 3 hours of manual reconciliation across 40+ records. We fixed it with pessimistic locking, not OT, but the incident made last-write-wins viscerally dangerous rather than abstractly dangerous. The investigation was what finally convinced the team.

What I’m Learning#

OT sounds simple until you write transformation functions for anything beyond plain text. The server coordinator requirement is not optional: it is part of the correctness model.

Have you ever debugged a concurrent-edit issue in production? Did you reach for locking, or something more sophisticated?