Design Google Docs

HardMessaging & CollaborationOperational TransformationCRDTWebSocketsReal-Time Collaboration

Problem

Design a collaborative document editor like Google Docs. Several people can edit the same document at the same time, and everyone sees the others' changes within about a second. Concurrent edits must never be lost or leave collaborators with different text. Users see each other's cursors and selections, documents are saved automatically, and users can browse and restore earlier versions. Documents are shared with view, comment, or edit permission. The product has 50 million daily active users and 1 billion documents, and a document can have up to 100 simultaneous editors.

  • What is the maximum number of simultaneous editors per document, and of viewers?
  • Must editing work offline, with changes merged when the user reconnects?
  • Is the content plain rich text, or do we also need tables, images, and embedded objects?
  • How granular should version history be: every keystroke, or named snapshots every few minutes?
  • Are comments and suggestions (tracked changes) in scope?
  • What is the largest document size we must support?

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • Edit a document concurrently with other users, with changes shown in near real time.
  • Guarantee that all collaborators converge to the same content without losing edits.
  • Show other collaborators' cursors and selections.
  • Save automatically, with no save button.
  • Browse version history and restore an earlier version.
  • Share documents with view, comment, or edit permission.
  • Add comments anchored to ranges of text.
  • Allow short offline editing that syncs on reconnect.

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • Remote edits visible within 1 second; local typing must feel instant (under 50 ms).
  • Zero lost edits once the client shows "Saved".
  • 50 million daily users with perhaps 5 million documents open at peak, and around 1 million being actively edited.
  • An active editor sends a batched operation every 200 ms while typing, so peak load is a few million operations per second across all documents, but each document sees at most a few hundred per second.
  • Storage: 1 billion documents at an average of 50 KB of snapshots plus compacted history is about 50 TB, before images.
  • High availability for opening and reading documents; brief editing interruptions during failover are acceptable if no edit is lost.

Each actively edited document is owned by exactly one session server, chosen by consistent hashing, which serializes all incoming operations, transforms them against concurrent ones using operational transformation (OT), appends them to the document's operation log, and broadcasts them to connected clients. Clients apply their own edits immediately and keep unacknowledged operations in a pending buffer, transforming incoming remote operations around them so every replica converges. Periodic snapshots bound the log, power fast loading, and form the version history.

Loading diagram...

Open a document. The client calls GET /docs/:id, which checks permission and returns the latest snapshot plus its revision number. The client then opens a WebSocket through the session router, which forwards it to the session server that owns this document (loading the document into memory there if it was not active). The server sends any operations newer than the snapshot revision.

Local edit. The user types. The client applies the change locally at once, records it as an operation such as insert(pos 120, "hello"), and sends it tagged with the revision it was based on. While waiting for the acknowledgment, further keystrokes are combined into a buffer that is sent next.

Server ordering. The session server receives the operation based on revision 41 when the document is at revision 43. It transforms the operation against operations 42 and 43 so its positions are correct, assigns revision 44, appends it to the operation log, acknowledges the sender, and broadcasts the transformed operation to the other clients.

Remote edit on the client. A client that still has unacknowledged local operations transforms the incoming remote operation against them before applying it, and transforms its pending operations against the remote one. Every client therefore ends with the same text.

Cursors. Clients send cursor and selection positions to the session server, which relays them to others and keeps them in a short-lived presence cache. Cursor positions are transformed by edits like any other position.

Snapshots and history. A snapshot worker periodically replays new operations onto the last snapshot and stores the result, tagged with the revision and the contributing authors. Version history lists these snapshots, and restore creates a new revision whose content equals the chosen snapshot.

Single owner for each active document, responsible for ordering and transforming operations.

  • Stateful Java or Go servers holding open documents in memory.
  • Consistent hashing on document ID with a coordination service (ZooKeeper or etcd) to lease document ownership.

Diagram

Loading diagram...

Scalability

Each document is small and edited by at most around 100 people, so one server handles thousands of active documents. Load scales by adding servers and moving ownership. If a server dies, its lease expires, another server takes over, rebuilds state from the latest snapshot plus the operation log, and clients reconnect and resend unacknowledged operations, which the server deduplicates by client ID and sequence.

Tradeoffs

A single owner per document makes ordering trivial and keeps the server-side OT simple, because the server only needs to transform against a linear history. A peer-to-peer CRDT approach (such as Yjs or Automerge) removes the central ordering point and handles long offline edits better, but carries metadata overhead per character and makes access control and server-side features such as comments harder. For a centrally hosted, online-first editor, OT with a central server is the proven, compact choice.

Durable, append-only record of every accepted operation for each document.

  • A wide-column store such as Bigtable or Cassandra, or a sharded relational table.

Data Model

  • ops: doc_id (partition key), revision (clustering key), author_id, client_id, client_seq, operation (compact binary), created_at.

Scalability

Appends are sequential per document and partitioned by document ID, so writes spread evenly. The session server acknowledges an operation only after the append is durable, which is what makes "Saved" a real guarantee. Operations older than the latest snapshots are compacted after 30 days, keeping only snapshots for older history.

Tradeoffs

Storing operations rather than full document versions on every change keeps writes tiny. The cost is replay time when loading, which snapshots bound.

Stores periodic full copies of each document for fast loading and for version history.

  • Object storage for snapshot bodies and a metadata table for the version list.

Data Model

  • snapshots(doc_id, revision, storage_key, authors, created_at, is_named)

Scalability

Snapshots are taken every few hundred operations or after a few minutes of idle time, so loading replays at most a few hundred operations. Older versions are thinned: every snapshot for a day, then hourly, then daily.

Tradeoffs

Showing every keystroke as a version would be noisy and expensive; grouping by time and author matches how people think about history.

Handles create, open, share, list, and search of documents outside the live editing path.

API

  • POST /docs creates a document and returns { docId }.
  • GET /docs/:id returns { snapshot, revision, permissions }.
  • POST /docs/:id/share with { email, role }.
  • GET /docs/:id/versions and POST /docs/:id/versions/:rev/restore.

Data Model

  • documents(doc_id, owner_id, title, created_at, updated_at)
  • acl(doc_id, principal_id, role) with roles viewer, commenter, editor.

Tradeoffs

Permission checks happen at open time and are cached on the session server; when sharing changes, the API notifies the session server to recheck connected users, so a revoked editor is cut off within seconds rather than at next reload.

Short-lived state for who is viewing a document and where their cursor is.

  • In-memory on the session server, mirrored to Redis with a short TTL for failover.

Tradeoffs

Cursor updates are high volume and worthless after a few seconds, so they are never written to the durable log. Losing them on failover only means cursors briefly disappear.

Related questions