Design Google Docs
Chat delivers whole messages; Google Docs merges keystrokes. Two people type into the same sentence at the same moment, and both must see a document that makes sense, converges to the same text, and loses neither person's work. That one requirement (concurrent edits to shared state) is what separates this question from every messaging design, and it's why interviewers use it to test whether you understand conflict resolution rather than just transport.
Clarifying the requirements
- Concurrency scale? Tens of simultaneous editors per document is the real product shape (plus hundreds of viewers). Not thousands; saying this bounds the design honestly.
- What must converge? Text edits, cursors/selections, comments, formatting. Character-level merging applies to text; comments and formatting can ride simpler paths.
- Offline? Yes: edit on a plane, merge on reconnect. This decision, more than any other, stresses the conflict model.
- History? Version history with restore, and "who wrote this" attribution.
- Latency feel? Local echo must be instant; remote edits should appear within ~100-200ms in-region.
Back-of-envelope numbers
Assume 100M active documents per day, with about 1% being edited concurrently.
- Per document: an active editor produces a few operations per second, so 10 editors is roughly on a hot document, which is tiny
- Fleet-wide: live editing sessions
- Write volume: at roughly 100 B per operation, a busy document writes about to its log
- Log growth: a year of edits on a heavy document reaches millions of operations, which makes snapshots a latency requirement rather than an optimization
The numbers say the load per document is trivial and the fleet-wide problem is session count. The design difficulty is logical (merging), not throughput, and stating that early frames the whole interview correctly.
High-level architecture
- Gateways. The real-time pattern's connection tier, unchanged: sockets, heartbeats, dumb forwarding.
- Doc sequencer. The design's heart. Each live document is assigned (by consistent hashing) to one coordinator that receives ops, transforms them against concurrent ops, assigns each a sequence number, and broadcasts the result. One authority per doc makes ordering trivial.
- Op log. The append-only record of every transformed op, in sequence order. The source of truth; the document is this log.
- Snapshots. Periodic materializations of the log (say every 500 ops), so opening a doc is snapshot + tail replay, not a million-op replay.
Deep dive 1: why last-write-wins destroys work, and what OT does instead
Start with the failure. The doc says cat. Alice types s at position 3 (cats) while Bob types my at position 0 (my cat). Ship both edits as "replace the document" and one of them vanishes. Ship both as raw positional inserts and Bob's edit shifts Alice's target: applying insert('s', 3) after my landed gives my scat. Positions are relative to a document state, and concurrent edits invalidate each other's positions.
Operational transformation (OT) fixes the positions at merge time. The sequencer receives Alice's insert('s', 3), sees it was made against sequence 41 while Bob's op already became 42, and transforms it: Bob inserted 3 characters at position 0, so Alice's insert shifts to position 6, producing my cats. Every client applies ops in sequencer order, transforming their own in-flight ops the same way, and all replicas provably converge. The client types with zero latency (local echo), sends ops asynchronously, and reconciles when the transformed versions come back.
The alternative family is CRDTs, where operations are designed to commute so replicas converge without a central sequencer. CRDTs shine for peer-to-peer and offline-first designs, and cost more in metadata and data-structure complexity. With Google Docs' shape (server-mediated, one home per doc), OT with a sequencer is the simpler defensible choice; naming both and choosing with a reason is what the interviewer wants. And per the judgment call, say where you need neither: cursors and presence are idempotent overwrites, and comments are append-only objects, so only the text stream pays the OT toll.
Deep dive 2: the op log, snapshots, and history
The document's source of truth is the op log: an append-only, ordered list of every operation ever applied to the document, each stamped with its sequence number and its author. The current document is not stored as a document at all — it's whatever you get by replaying that log from the beginning.
Storing the document this way gives you three capabilities, and it's worth naming all three:
- Fast open. Replaying a million operations on every open would be far too slow, so the system periodically stores a snapshot of the document state at a given sequence number. Opening a document means loading the most recent snapshot and replaying only the operations after it, so a document with a long history still opens from a few hundred operations.
- Version history. Any past state of the document is just a snapshot plus a replay up to some sequence number. Attribution comes along for free, since every operation carries the ID of the user who made it.
- Restore without rewriting history. Restoring an old version is implemented as a new operation, or batch of operations, that transforms the current state into the old one. The log stays append-only, so the audit trail is never edited.
Snapshotting runs as a background consumer of the log. Retention is a real decision worth raising: a tenant under legal hold may need every operation kept indefinitely, while for everyone else you can coalesce fine-grained history older than 30 days into periodic checkpoints.
Deep dive 3: offline editing and rejoin
A user edits for an hour on a plane: 2,000 local ops against a document that meanwhile advanced 500 ops. On reconnect, the client submits its op batch anchored at its last-known sequence; the sequencer transforms the batch across the 500 intervening ops and appends. OT handles this mechanically, but two practical limits earn discussion. First, transform cost grows with divergence, so bound it: beyond a threshold, fall back to a three-way merge UI for the pathological cases (weeks offline against heavy edits) rather than pretending silent merges are always right. Second, intent drift: a perfectly transformed edit can still land in a paragraph whose meaning changed. Convergence guarantees consistency, not intent, and acknowledging that boundary (with history and attribution as the recovery tools) is a staff-level honesty signal.
Deep dive 4: what happens when the sequencer dies?
Assigning one sequencer per document makes ordering trivial, and it also creates the obvious question an interviewer will ask: that sequencer is a single point of failure for the document, so what happens when it crashes?
How a document gets its sequencer. Documents are mapped to a pool of coordinator servers by consistent hashing on doc_id (routing and replication). A sequencer starts up when someone first opens the document and shuts down after the document has been idle for a while, so you're only paying for documents people are actually editing.
Recovering from a crash. Because the op log is durable and the sequencer holds only in-memory state, recovery is a matter of rebuilding that state. A replacement sequencer is assigned the document, reads the tail of the log, and reconstructs where the document left off. Meanwhile, clients that sent operations but never received an acknowledgement simply resend them. Each operation carries a (client_id, op_id) pair, so an operation that actually did make it into the log before the crash is recognized as a duplicate and dropped rather than applied twice.
Preventing two sequencers at once. The dangerous failure isn't a crash, it's a network partition, where the original sequencer is still running and still assigning sequence numbers while a replacement has been appointed. Two sequencers issuing numbers for the same document would corrupt the ordering the entire design depends on. The fix is fencing: each sequencer is issued an epoch number when it takes over, every write to the log carries that epoch, and the log rejects any write carrying an epoch older than the current one. The partitioned sequencer's writes fail, and it learns it has been replaced.
A document with many viewers. A company all-hands agenda might have 50 people editing and 5,000 watching. Keep editors connected to the sequencer, and serve viewers from a broadcast stream instead, which is the same fan-out inversion that Design Slack uses for very large channels.
Leveling the answer
Related lessons
The messaging-shaped sibling, whose fan-out inversion applies to documents with many viewers.
Per-user delivery state and reconnection, the parts a document editor handles differently.