Google Docs#
Problem statement (interviewer prompt)
Design Google Docs: many users edit the same document simultaneously and see each others' edits + cursors in real time. Pick between Operational Transform (OT) and CRDTs, handle offline edits + sync, comments + suggestions, and revision history.
flowchart LR
A([User A])
B([User B])
WS[WebSocket Gateway]
COL[Collab Service<br/>OT / CRDT]
DOC[(Doc state + history)]
A --> WS --> COL --> DOC
B --> WS
COL --> WS --> B
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class A,B client;
class WS edge;
class COL service;
class DOC datastore;
flowchart TB
subgraph Clients
EDIT[Editor app]
PRES[Presence]
CUR[Cursor / selection]
end
subgraph Edge
LB
WS[WebSocket gateway<br/>pinned per doc]
end
subgraph Collab[Collaboration engine]
OT[OT engine<br/>transform op against concurrent ops]
CRDT_ENG[CRDT engine<br/>Yjs / Automerge style]
SESS[Session manager]
REV[Revision log per doc]
SNAP[Snapshots]
LOCK[Per-doc serialization]
end
subgraph Storage
DOC[(Doc store)]
OPLOG[(Op log)]
META[(Permissions, comments, suggestions)]
end
subgraph Features
PRES_S[Presence service]
COMM[Comments / Threads]
SUGG[Suggesting mode]
VER[Version history]
EXPORT[Export PDF / Word]
OFF[Offline support]
end
subgraph Share
ACL[ACL / link sharing]
AUTH[Auth + tenant]
end
Clients --> LB --> WS --> Collab
Collab --> Storage
Features --- Collab
Share --- Storage
classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
class WS edge;
class EDIT,PRES,CUR,OT,CRDT_ENG,SESS,REV,SNAP,LOCK,PRES_S,COMM,SUGG,VER,EXPORT,OFF,ACL,AUTH service;
class DOC,OPLOG,META datastore;
OT vs CRDT#
- Operational Transform (Google Docs historically): server orders ops and transforms incoming ops against concurrent committed ops.
- CRDT (Yjs, Automerge): each op has unique ID with causal context; merges by mathematical guarantees.
Per-doc serializer#
- One server owns a doc at a time → serializes ops in a definite order.
- Failover to another server reloads from op log.
Presence#
- Lightweight; per-doc pub/sub channel.
- Cursors and selections sent at 10 Hz max with throttling.
Glossary & fundamentals#
Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.
| Tag | Concept | What it is | Page |
|---|---|---|---|
HLD |
Pub/Sub & message brokers | topics, consumer groups, delivery semantics | pub-sub-pattern |
HLD |
CRDTs | commutative replicated data types | crdts |
HLD |
Realtime protocols | WS / SSE / polling / gRPC streaming | realtime-protocols |
LLD |
Async models | futures / async-await / coroutines / actors | async-models |
Quick reference#
Functional#
- Concurrent editing with sub-second sync.
- Cursor + selection presence.
- Comments, suggesting, version history.
- Offline mode + reconciliation.
- Sharing, link tokens, ACLs.
Non-functional#
- p99 op echo < 200 ms.
- 100+ concurrent editors per doc.
- 99.99% uptime.
Trade-offs#
- OT vs CRDT: OT mature with central server; CRDT decentralizable + offline-friendly.
- Per-doc serialization simplifies correctness; failover challenge.
- Storage: op log → periodic snapshots to bound replay cost.
Refs#
- Google Wave / Docs OT papers.
- Yjs / Automerge / RGA papers.
- ByteByteGo "Design Google Docs".
FAQ#
How does Google Docs work in real time?#
Every keystroke is sent as an operation over WebSocket to a collab server. The server transforms concurrent ops with OT and broadcasts the result to all clients, keeping every replica converged.
What is Operational Transformation in Google Docs?#
OT rewrites concurrent ops so they apply in the same order on every replica. If Alice inserts at index 3 and Bob inserts at index 5 simultaneously, the server adjusts indices so both edits land correctly.
OT vs CRDT for Google Docs?#
Google chose OT because it produces compact ops and a central server scales well for the editor's traffic. CRDTs trade larger payloads for peer-to-peer and offline-first merges.
How does Google Docs handle offline edits?#
Offline ops are buffered locally with a base revision tag. On reconnect they replay against the server, which transforms each against any newer ops before applying them.
How is Google Docs revision history stored?#
The collab server appends every op to an ordered log per document. Revision history reconstructs any past state by replaying ops up to a target version.
Further reading#
Curated, high-credibility sources for going deeper on this topic.
- 📄 Paper - Sun & Ellis - Operational Transformation in Real-Time Group Editors
- ✍️ Blog - Martin Kleppmann - CRDTs: the hard parts
- 📑 Docs - Yjs - high-performance CRDT
- ✍️ Blog - Figma - How Figma's multiplayer technology works