How does real-time collaborative editing handle simultaneous modifications?

I’m working on a real-time collaborative text editor in JavaScript and I’m stuck on handling conflicts when multiple users edit simultaneously.

Here’s my scenario: User X and User Y are both editing with 15ms network latency. The system uses operations like “add ‘word’ at position 5” with timestamps for ordering.

Starting document: “dog456”

User X adds “cat” at beginning (timestamp 002ms)
User Y adds “world” between “dog” and “456” (timestamp 006ms)

Expected result: “catdogworld456”

But here’s what actually happens:

  • User Y gets X’s edit at 017ms. To maintain chronological order, Y undoes their edit, applies X’s change, then reapplies their edit at the same position. Result: “catworlddog456”
  • User X gets Y’s edit at 021ms and applies it at position 5, giving “catworlddog456”

The problem is both users end up with “catworlddog456” instead of “catdogworld456”.

I’ve considered several approaches:

  1. Track cursor positions instead of indices, but moving cursors creates the same issue
  2. Send context like “insert after ‘dog’” but this fails if someone adds “dog” elsewhere
  3. Use a central server for timestamps, but this adds complexity
  4. Assign unique IDs to each character, but this seems excessive

What’s the standard approach for solving these transformation conflicts in collaborative editors?

you’re definitely overthinking the timestamp approach. just use a simple vector state where each operation includes the document version it was made against. when conflicts pop up, the user with the lower id wins at that position. works great for most situations and it’s way simpler than full ot.

You’re treating operations like they’re independent when they’re actually interdependent transformations. I ran into this same mess building a collaborative code editor last year. Operations need to be transformed relative to each other - timestamps alone won’t cut it. When User Y undoes and reapplies their operation, they’re hitting a different document state but using the old position. Here’s what works: implement a simplified OT where each operation tracks the document state it was created against. When an operation comes in, calculate how much all prior operations shifted the positions, then adjust accordingly. In your example, User Y’s operation should know that inserting “cat” at position 0 pushes everything else down by 3 characters. So “world” goes at position 6, not 3. Vector clocks beat timestamps for conflict resolution and don’t need a central server.

You’re encountering a classic problem that Operational Transformation (OT) algorithms solve. Your timestamp approach is flawed as it fails to address operation transformations when they are applied out of order. Here’s what’s really happening: when User Y receives User X’s operation and attempts to reapply their own, the position indices change, and your system doesn’t adjust accordingly. With a proper OT system, each incoming operation must transform all subsequent operations. In your example, when User Y processes ‘add cat at position 0’, their pending ‘add world at position 3’ should transform to ‘add world at position 6’ because the insertion point has moved by 3 characters. I recommend implementing a genuine OT algorithm (like the one used by Google Docs) or exploring Conflict-free Replicated Data Types (CRDTs) which are typically easier to implement. Libraries like ShareJS or Yjs provide robust implementations that manage these edge cases effectively.