What would be your approach for handling sync conflicts and operational transforms
For collaborative editing, use CRDTs (Yjs/Automerge) or Operational Transformation to merge concurrent edits without conflicts. Send operations not snapshots, keep edits commutative/convergent, apply optimistic local updates, and reconcile via a server. For simpler cases, last-write-wins or field-level locking.
Sync conflicts happen when two clients edit the same data concurrently. The right approach depends on how rich the concurrency is.
Match the strategy to the problem
| Scenario | Strategy |
|---|---|
| Rarely-concurrent fields, low stakes | Last-write-wins (timestamp/version) |
| Distinct fields edited separately | Field-level merge / locking |
| Concurrent edits to the same structure (forms, lists) | Operational Transformation (OT) or CRDT |
| Rich collaborative text/documents | CRDT (Yjs, Automerge) — modern default |
Don't reach for OT/CRDT if a version check would do — it's a lot of complexity.
Operational Transformation (OT)
- Clients send operations ("insert 'x' at index 5"), not document snapshots.
- The server (and clients) transform incoming ops against ops that happened concurrently, so they apply correctly against the current state. Two users inserting at the same index get transformed so both insertions land coherently.
- Powerful (Google Docs uses it) but the transform functions are notoriously hard to get right; typically needs a central server.
CRDTs (the modern default)
- Conflict-free Replicated Data Types: data structures mathematically guaranteed to converge — apply ops in any order, on any replica, and everyone ends up identical.
- Yjs and Automerge are production-ready. Yjs is fast and has a rich ecosystem (text, awareness, many editor bindings).
- Pros: no central transform server required (peer-to-peer capable), offline-friendly, composable. Cons: metadata overhead, "convergent" isn't always the semantically desired merge.
Cross-cutting principles
- Send operations/deltas, not whole snapshots — snapshots can't merge.
- Optimistic local apply — apply the user's edit immediately, sync in the background, reconcile.
- Version vectors / sequence numbers — detect concurrency and order.
- Idempotency — re-delivered ops must not double-apply.
- Convergence ≠ correctness — everyone agreeing on a result doesn't mean it's the result a human wanted. For some domains, surface the conflict to the user instead of auto-merging.
- Persistence & history — store the op log; it gives you undo, audit, and recovery.
Practical recommendation
For real collaborative editing, use Yjs (or Automerge) rather than hand-rolling OT — getting transforms provably correct is a research-grade problem. For simple apps, version-based last-write-wins with a clear "this changed underneath you" UX is often enough.
Follow-up questions
- •OT vs CRDT — what are the real tradeoffs?
- •Why send operations instead of document snapshots?
- •When is last-write-wins actually fine?
- •Why is 'convergence' not the same as 'correctness'?
Common mistakes
- •Reaching for OT/CRDT when a simple version check would suffice.
- •Syncing whole snapshots that can't be merged.
- •Hand-rolling OT transform functions and getting edge cases wrong.
- •Assuming auto-merge always produces the semantically right result.
Performance considerations
- •CRDTs carry per-element metadata — memory and payload overhead grows with edit history; needs GC/compaction. OT keeps documents lean but pushes cost onto the transform server. Optimistic local apply keeps the UI instant regardless.
Edge cases
- •Offline edits that must merge on reconnect.
- •Concurrent insert at the same position.
- •One client deletes a region another is editing.
- •Op log growth — needs compaction/garbage collection.
Real-world examples
- •Google Docs (OT), Figma (custom CRDT-like), Linear/Notion-style apps using Yjs or Automerge.
- •Yjs powering collaborative cursors + text in editors like TipTap/ProseMirror.