System Design: Design a Collaborative Document Editor
Design a real-time collaborative editor like Google Docs. Covers conflict resolution with CRDTs and OT, presence awareness, cursor synchronization, and offline editing support.
What you'll learn
- ✓Resolve concurrent edits using OT or CRDTs
- ✓Synchronize cursors and presence across collaborators
- ✓Design document storage with version history
- ✓Handle offline editing and reconnection
- ✓Scale WebSocket connections for real-time collaboration
Prerequisites
None — this post is self-contained.
Real-time collaborative editing lets multiple people edit the same document simultaneously, seeing each other’s changes as they type. Google Docs, Notion, and Figma all solve this problem. The core challenge is deceptively hard: when two users type at the same position at the same time, how do you merge their changes into a consistent result without losing either person’s work?
This is fundamentally a distributed consensus problem applied to text, and the solutions — Operational Transformation and Conflict-free Replicated Data Types — are among the most elegant algorithms in distributed systems.
Functional Requirements
- Multiple users can edit the same document simultaneously.
- Changes appear on all connected clients within 200ms.
- Show each collaborator’s cursor position and selection in real time.
- Display a presence indicator (who is currently viewing the document).
- Maintain a complete version history with the ability to view and restore previous versions.
- Support offline editing with automatic conflict resolution on reconnection.
Non-Functional Requirements
- Latency: local edits should appear instantly (optimistic local application). Remote edits should arrive within 200ms.
- Consistency: all clients must converge to the same document state, regardless of the order in which they receive operations.
- Scalability: support documents with up to 100 concurrent editors and a system serving millions of documents.
- Durability: never lose a user’s edits, even during server failures.
High-Level Architecture
The system has four components:
- Client editor — a rich text editor in the browser that applies edits locally and sends operations to the server.
- Collaboration server — manages active editing sessions, relays operations between clients, and resolves conflicts.
- Document storage — persists document content and version history.
- Presence service — tracks which users are in which documents with their cursor positions.
Clients connect to the collaboration server via WebSocket. Each edit generates an operation (insert character at position 5, delete characters 10-15, apply bold to range 20-30). The client applies the operation locally for instant feedback, then sends it to the server. The server transforms the operation against any concurrent operations, applies it to the canonical document state, and broadcasts it to all other connected clients.
Conflict Resolution: Operational Transformation
Operational Transformation (OT) is the classic approach, used by Google Docs. The core idea: when two concurrent operations conflict, transform one against the other so that applying them in either order produces the same result.
Example. Two users start with the text “abc”. User A inserts “X” at position 1 (result: “aXbc”). Concurrently, User B deletes position 2 (the “c”, result: “ab”). Without transformation, applying A’s operation then B’s would give “aXb” (delete position 2, which is now “b”), but applying B’s then A’s would give “aXb” too — only by luck. For other cases, naive application produces divergent states.
The transform function adjusts operations based on concurrent operations. If A inserted at position 1 and B deleted at position 2, then B’s operation, as seen by A, becomes delete at position 3 (because A’s insertion shifted everything after position 1 by one).
Server as the sequencer. The server assigns a sequential version number to each operation. Each client tracks the server version it has seen. When a client sends an operation, it includes the server version it was based on. The server transforms the incoming operation against all operations that have been applied since that version, applies the transformed operation, increments the version, and broadcasts the result.
OT is well-proven but complex. The transformation functions must handle every combination of operation types (insert/insert, insert/delete, delete/delete, format/insert, etc.), and getting them right is notoriously tricky.
Conflict Resolution: CRDTs
Conflict-free Replicated Data Types (CRDTs) are a newer approach that avoids the need for a central sequencer. Each character in the document is assigned a globally unique, ordered ID. Operations reference these IDs rather than positional indices, so concurrent operations never conflict.
Sequence CRDTs like YATA (used by Yjs) or RGA assign each character an ID composed of a logical clock and a unique client identifier. Inserting a character creates a new ID between the IDs of its neighbors. Because IDs are globally unique and their ordering is deterministic, all clients converge to the same sequence regardless of the order in which they receive operations.
Trade-offs:
- CRDTs do not require a central server for conflict resolution, making them suitable for peer-to-peer collaboration and offline editing.
- CRDTs have higher metadata overhead (each character carries an ID), but modern implementations (Yjs, Automerge) optimize this effectively.
- OT is simpler to implement for basic text but becomes complex for rich text. CRDTs handle rich text and complex document structures more naturally.
For a production collaborative editor, CRDTs (specifically Yjs) have become the dominant choice due to their robustness and offline support.
Document Model
The document is a tree structure:
Document
-> Block (paragraph, heading, list item, image, table)
-> Inline content (text runs with formatting attributes)
-> Characters (each with a unique CRDT ID)
Each block is a CRDT sequence of inline content. Each inline element is a CRDT sequence of characters. Formatting (bold, italic, link) is represented as attributes on character ranges.
This hierarchical model lets the CRDT handle not just text insertion and deletion but also structural changes like splitting paragraphs, merging list items, and embedding media.
Real-Time Communication
WebSocket connections. Each client maintains a WebSocket connection to the collaboration server. The server maintains a room per document, tracking all connected clients.
Operation broadcast. When the server receives an operation from a client:
- Apply the operation to the server’s document state.
- Assign a server version number.
- Broadcast the operation to all other clients in the room.
- Acknowledge the operation to the sending client with the assigned version.
Message format:
{
"type": "operation",
"doc_id": "doc-123",
"client_id": "client-456",
"version": 42,
"ops": [
{ "type": "insert", "position": { "after": "id-abc" }, "content": "Hello", "attributes": { "bold": true } }
]
}
Presence and Cursors
Presence information (who is in the document, where their cursor is) is ephemeral and does not need durability.
Each client sends cursor updates as lightweight messages over the same WebSocket connection:
{
"type": "cursor",
"client_id": "client-456",
"user": { "name": "Alice", "color": "#FF5733" },
"anchor": "crdt-id-100",
"head": "crdt-id-105"
}
The server broadcasts cursor updates to all other clients. Since cursor positions reference CRDT IDs (not numerical indices), they remain valid even after concurrent edits shift the document content.
Throttle cursor updates to at most 10 per second per client to reduce bandwidth.
Document Storage and Version History
Periodic snapshots. The collaboration server periodically (every 30 seconds or every 100 operations) saves a snapshot of the document to persistent storage. The snapshot includes the full CRDT state and the current server version.
Operation log. Store every operation in an append-only log alongside the document. This enables:
- Version history: reconstruct the document at any point in time by replaying operations from a snapshot.
- Recovery: if the collaboration server crashes, reload the latest snapshot and replay subsequent operations.
- Attribution: track which user made each change for “last edited by” and blame features.
Storage schema:
documents
id UUID
title VARCHAR
owner_id UUID
created_at TIMESTAMP
current_version BIGINT
document_snapshots
doc_id UUID
version BIGINT
crdt_state BYTEA (serialized CRDT)
created_at TIMESTAMP
document_operations
doc_id UUID
version BIGINT
client_id VARCHAR
ops JSONB
created_at TIMESTAMP
Offline Editing
CRDTs naturally support offline editing. When a client goes offline:
- The client continues applying edits to its local CRDT state.
- Operations are queued in a local buffer.
- When the client reconnects, it sends all buffered operations to the server.
- The CRDT merge algorithm automatically resolves any conflicts with edits made by other users while the client was offline.
- The client receives and applies any operations it missed.
No special conflict resolution logic is needed because CRDT merges are commutative, associative, and idempotent by design.
Scaling
WebSocket connection scaling. A single server can handle thousands of WebSocket connections. For millions of concurrent documents:
- Run a pool of collaboration servers behind a load balancer.
- Use sticky sessions (route by document ID) so all clients editing the same document connect to the same server.
- If a document has more editors than one server can handle, use a pub/sub system (Redis pub/sub) to relay operations between server instances.
Document sharding. Shard documents across collaboration servers by document ID hash. Each server handles a subset of active documents.
Cold documents. Documents not currently being edited do not need a collaboration server. Load the document state into a server on demand when the first client connects. Unload it after a period of inactivity.
Summary
A collaborative document editor combines a conflict resolution algorithm (OT or CRDTs), a real-time communication layer (WebSockets), and a persistence layer (snapshots plus operation logs). CRDTs have emerged as the preferred approach because they handle offline editing naturally and do not require a central sequencer. The critical design decisions are the document model (hierarchical CRDT structure), how you persist and recover state (snapshots plus operation replay), and how you scale WebSocket connections across many documents (sticky sessions with document-based sharding).
Related articles
- System Design System Design: Design a Content Delivery Network
Design a CDN that serves static and dynamic content from edge locations worldwide. Covers caching tiers, cache invalidation, origin shielding, and anycast routing.
- System Design System Design: Design a DNS System
Design a scalable Domain Name System. Covers recursive and authoritative resolution, zone file management, caching, anycast deployment, and handling billions of lookups daily.
- System Design System Design: Design an Object Storage System
Design an object storage system like Amazon S3. Covers metadata management, data placement, erasure coding, consistency models, and multi-tenant isolation at petabyte scale.
- System Design System Design: Ride Sharing Architecture
How ride sharing platforms match riders and drivers in real time: location ingestion, geospatial indexing, dispatch, and the trade-offs behind low latency matching.