Google Docs raised the baseline in 2006: simultaneous conflict-free editing, a teammate cursor gliding across your screen. Building real-time collaboration means choosing between OT and CRDTs, separating presence from persistence, and sizing the transport.
Why the naive approach fails
Last-write-wins drops keystrokes when two people edit the same paragraph. The latest write overwrites whole characters without warning. Document locking with one person at a time feels like a 1998 interface and defeats the purpose of a shared editor.
OT or CRDT
Operational Transformation transforms concurrent operations against server ordering. The design is complex and server-authoritative, with ShareDB as the classic implementation. CRDTs guarantee mathematical convergence without central coordination, and Yjs plus Automerge lead the mature implementations. OT concentrates intelligence on the server. CRDT distributes state across clients.
Yjs in practice
The document becomes shared types (Y.Text, Y.Map) manipulated like local structures. The provider layer abstracts the network: y-websockets for a dedicated server, y-webrtc for P2P. Offline edits accumulate on the client and merge on reconnect. The awareness API drives cursors, selections, and displayed names.
Presence indicator
Presence is ephemeral state: cursor position, active selection, typing status. This state lives outside CRDT persistence and dies with the session. A heartbeat every 15 seconds, paired with faster expiry, keeps the participant roster honest when someone closes the tab without saying goodbye.
WebSockets transport
WebSockets handle the transport. Binary encoding keeps bandwidth sane: Yjs updates stay under 100 bytes per keystroke batch. Broadcast rooms take scope by document ID, so the relay forwards updates just to whoever opened that document.
Server architecture
A stateless relay serves the start of the project. Without snapshotting, memory grows without limit; compact the document every N updates to contain size. Redis pub/sub fans out across relay instances, and sticky sessions by document ID prevent connection hops mid-edit.
Persistence
Debounced snapshots every 5 seconds go to object storage (S3, GCS) and capture current state. The update log stays retained for audit and recovery; restoring means replaying the log onto the last snapshot. The debounce balances write cost against loss risk.
Testing simultaneous editing
Deterministic simulation of delayed and reordered messages exposes convergence bugs unit tests never find. Property tests with two virtual clients and a fixed seed reproduce the scenario in CI as many times as you need.
Enjoyed this content?
I build web products and AI solutions the right way — solid architecture, maintainable code, and real delivery.
Let's talk