Vibe Engines
YouTube
System Design

Design Google Docs

Step 1 / 9

Learn system design by building a real-time collaborative editor like Google Docs step by step.

The numbers to beatconvergentend stateintentpreservedany ordersame result

The whole design, in writing

Learn system design by building a real-time collaborative editor like Google Docs step by step. An interactive guide covering the document model, conflict resolution with OT and CRDTs, op broadcast over WebSockets, persistence with an op log and snapshots, presence, offline sync, and access control.

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is a collaborative editor?

Two people open the same document and type at the same time. Each sees their own edits instantly and the other’s appear live, and somehow both end up looking at the exact same text. No “file is locked”, no overwriting each other’s work.

Editor Atypes
New in this step: Editor A.

Stop syncing whole documents and start syncing tiny operations — “insert ‘x’ at position 5”. A server orders everyone’s ops and resolves conflicts so every copy converges to the same state. Get that one idea right and the rest is plumbing.

What the new pieces do

Editor Aclient
One person’s browser. It applies edits locally for instant feedback, then sends them to the server — it can’t wait for a round-trip per keystroke.

Step 1 · The model

Edits are operations

If a save means “upload the whole document”, two people’s saves clobber each other and the last one wins. You can’t merge intent from snapshots — by then the information about what changed is gone.

Edit ServerWebSocket hubDocument Modelordered units
New in this step: Edit Server, Document Model.

Two people edit the same doc simultaneously. What does the client send on each change?

  1. Whole-document saves clobber each other — last write wins — and the information about WHAT changed is gone, so you can’t merge intent. Concurrent editing needs the changes themselves.

  2. Coarse diffs against a stale base still race and lose intent when positions shift, and they’re expensive to merge. You want the fine-grained edits as they happen.

  3. Model the doc as an ordered sequence and stream small ops applied locally for instant feedback. Ops preserve intent and are cheap to send, order and merge — the op stream is the source of truth.

Model the doc as an ordered sequence and every edit as a small op (insert/delete at a position). The editor applies ops locally for instant feedback and streams them to an Edit Server over a persistent connection.

What the new pieces do

Edit Serverbackend
Holds a persistent connection to every editor of a doc. It receives edits, orders them, resolves conflicts, and relays the result to everyone.
Document Modelservice
The doc as a sequence of characters/objects with stable positions. Edits are tiny ops — insert at p, delete at p — not whole-document saves.

Step 2 · The hard part

Resolving concurrent edits

A and B both edit from the same starting text. A inserts at position 2; B deletes at position 1. By the time A’s op reaches B, B’s positions have shifted — apply A’s op blindly and the docs diverge. Forever.

Edit ServerWebSocket hubOT / CRDTtransform opsDocument Modelops + versions
New in this step: OT / CRDT.

A inserts at position 2; B deletes at position 1. By the time A’s op reaches B, positions have shifted. How do they stay identical?

  1. Locking kills real-time collaboration — it’s back to "file is locked," the exact thing you’re replacing. Concurrent edits must be allowed and reconciled, not serialized.

  2. OT rewrites an incoming op against ops it didn’t see (shift A’s "insert at 2" past B’s delete); CRDTs give each char a stable orderable id so edits merge commutatively. Both guarantee convergence — same ops, any order, same doc.

  3. Applying ops blindly when their positions were computed against different states makes the documents diverge permanently. Concurrent ops must be transformed/merged, not applied as-is.

Use Operational Transformation (OT) or CRDTs. OT rewrites an incoming op against ops it didn’t see (shift A’s “insert at 2” to account for B’s delete). CRDTs give each character a stable, orderable ID so concurrent edits merge commutatively — no transform needed.

  • convergentend state
  • intentpreserved
  • any ordersame result

What the new pieces do

OT / CRDTservice
The conflict-resolution core. It rewrites concurrent edits so that applying them in any order yields the same document on every screen.

Back of the envelope

same ops, any order ⇒ same doc
the convergence guarantee
OT: transform vs unseen ops
small ops, the server orders them
CRDT: stable per-char ids
commutative merge, no central transform

Step 3 · Make it live

Broadcast to everyone

A’s edit is resolved on the server — but B is still staring at stale text. Collaboration only feels real if the other person’s changes show up in well under a second.

Editor BOp Broadcast
New in this step: Editor B, Op Broadcast. · swipe to pan the diagram

A’s edit is resolved on the server, but B still sees stale text. How does B’s screen update live?

  1. Polling is laggy and wasteful — collaboration needs sub-second updates, and most polls return nothing. The server should push the moment an op is accepted.

  2. Re-downloading the doc is heavy, loses B’s cursor/scroll, and still lags. You want just the new ops pushed incrementally, not full reloads.

  3. After accepting an op the server pushes it to every other editor’s open socket; when editors are on different servers, pub/sub relays it to whoever holds each connection. Real-time = push, not poll.

After accepting an op, the server broadcasts it to every other editor of the doc over their WebSockets. When editors sit on different servers, a pub/sub layer relays the op to whichever server holds each connection.

What the new pieces do

Editor Bclient
A second person editing the same document at the same time. The whole challenge is making A’s and B’s concurrent edits converge to one consistent doc.
Op Broadcastbus
Pushes each accepted op to all other editors of the doc in real time. Across servers, a pub/sub layer relays ops to whoever holds the other connections.

Step 4 · Never lose a keystroke

Persist: op log + snapshots

The live document lives in server memory. A crash, and hours of collaborative work vanish. But you also can’t rebuild a long doc by replaying millions of ops on every open.

OT / CRDTtransform opsDocument Modelops + versionsOp Logevery changeSnapshotsperiodic state
New in this step: Op Log, Snapshots.

The live doc is in server memory and a long doc has millions of ops. How do you avoid losing work AND loading fast?

  1. The append-only op log is the truth (nothing lost on a crash); periodic snapshots let you load the latest checkpoint and replay only ops since — fast recovery, full history, free version control.

  2. Rewriting the whole doc per keystroke is enormous write amplification and still loses concurrent intent. Append the small op, and materialize full state only occasionally.

  3. Memory-only loses everything on a crash, and replaying millions of ops from scratch on every open is far too slow. You need durability (log) plus checkpoints (snapshots).

Append every accepted op to a durable Op Log, and periodically write a Snapshot of the full state. To load a doc, take the latest snapshot and replay only the ops since — fast recovery, full history, and free version control.

What the new pieces do

Op Logstore
An append-only history of every operation. Replaying it rebuilds the document and powers undo, version history, and catching up after disconnects.
Snapshotsstore
Periodic materialized copies of the doc so you don’t replay millions of ops from scratch — load the latest snapshot, then apply ops since.

Back of the envelope

append op = the truth
nothing lost on a crash
snapshot = checkpoint of the log
load latest + replay ops since
log ⇒ undo, history, catch-up
free version control

Step 5 · Who else is here?

Presence & cursors

Collaboration feels lonely — and people collide — if you can’t see where others are. Those colored cursors and name flags are core to the experience, but they’re high-frequency and disposable.

Edit ServerWebSocket hubDocument Modelops + versionsPresencecursors · who
New in this step: Presence.

Colored cursors and "3 people viewing" update constantly and are disposable. How do you handle them?

  1. Persisting a flood of high-frequency cursor updates would swamp the durable log with data nobody replays. Presence is throwaway — it shouldn’t touch document storage.

  2. Mixing disposable cursor traffic into the durable op pipeline risks the document during a presence storm. Keep ephemeral signals on a separate path.

  3. Track who’s connected and each cursor/selection, broadcast constantly, but never store it — if everyone leaves, presence evaporates while the doc remains. A cursor flood can’t risk the op log.

Track Presence separately from the document: who’s connected, and each person’s cursor/selection. Broadcast these constantly but never persist them — if everyone leaves, presence simply evaporates while the doc remains.

What the new pieces do

Presenceservice
Tracks who’s viewing, where their cursor and selection are, and broadcasts those colored carets — ephemeral state that never touches durable storage.

Step 6 · The flaky network

Offline & reconnection

Connections drop in tunnels and on planes. A user keeps typing offline, then reconnects — meanwhile others changed the doc. Their queued edits must merge cleanly, not overwrite or get lost.

Editor AEditor BEdit ServerOT / CRDTDocument ModelOp LogSnapshotsPresence
New in this step: Editor B → Op Log. · swipe to pan the diagram

A user edits offline in a tunnel, then reconnects — others changed the doc meanwhile. How do the edits merge?

  1. On reconnect the client sends queued ops and pulls everything from the op log after its last-seen version; OT/CRDT merges the two streams. Offline editing is just real-time editing with a longer delay — same convergence machinery.

  2. Overwriting discards everyone else’s changes made while the user was offline. Concurrent edits must merge, not clobber — exactly what OT/CRDT exists to do.

  3. Forbidding offline edits breaks the experience on planes and in tunnels, and isn’t necessary — buffered ops plus version-based catch-up merge cleanly on reconnect.

Each client tracks the last version it saw. On reconnect it sends its buffered ops and pulls everything from the Op Log since that version; OT/CRDT merges the two streams. The same convergence machinery that powers live editing powers catch-up.

Back of the envelope

client tracks last version N
sync = "give me ops after N"
buffered ops ⇄ pulled ops
OT/CRDT merges both streams
offline = real-time + delay
one mechanism for both

Step 7 · Permissions & scale

The sharp edges

Not everyone may edit — some can only view or comment — and a viewer must not be able to sneak in an op. And a server can only hold so many live docs and connections before it tips over.

Edit ServerDocument ModelSnapshotsPresencePermissions
New in this step: Permissions. · swipe to pan the diagram

Check Permissions on join and on every op, so the server rejects edits from non-editors. Scale by sharding by document: all editors of one doc land on the same server (consistent hashing), keeping its in-memory state authoritative; pub/sub bridges across shards.

What the new pieces do

Permissionsstore
Who may open, comment on, or edit a doc. Checked when joining and on every op, so a viewer’s stray edit is rejected at the server.

You did it

You just designed Google Docs.

Editor AEditor BEdit ServerOT / CRDTDocument ModelOp LogSnapshotsPresencePermissionsOp Broadcast
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Edits are tiny operations on an ordered model, not whole-document saves.
  • OT or CRDTs resolve concurrent edits so every copy converges.
  • Accepted ops broadcast over WebSockets (and pub/sub) for live collaboration.
  • A durable op log plus periodic snapshots give recovery, history and fast loads.
  • Presence (cursors, who’s here) runs on a separate, ephemeral path.
  • Versioned op logs make offline editing and reconnection sync just work.
  • Server-side permissions and per-document sharding handle security and scale.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. OT vs CRDT — which would you choose and why?

    OT centralizes ordering on a server and keeps ops tiny, which is why Google Docs uses it — but the transform functions are notoriously hard to get right for rich content. CRDTs need no central transform and shine for peer-to-peer/offline-first (each character carries a stable id), at the cost of more per-character metadata and memory. Central server + rich text → OT; decentralized/offline-first → CRDT.

  2. How does undo work with concurrent editors?

    Undo must be selective: it reverts your own last op, not whoever edited most recently. You generate the inverse of your operation and transform it against everything since — so undoing your insert doesn’t clobber a collaborator’s later edit. The op log makes this possible because every change (and its author) is recorded in order.

  3. Why pin a whole document to one server (shard by doc)?

    It avoids distributed consensus on every keystroke. With all editors of a doc on one authoritative server, that server alone orders ops and holds the in-memory state — fast and simple; pub/sub bridges to editors connected elsewhere. Sharding by doc (consistent hashing) also spreads load and lets a hot doc’s server scale independently.

  4. How do comments and suggestions fit the op model?

    They’re ops too, but anchored to positions/ranges rather than mutating text. A comment references a span (via stable ids so it survives edits); "suggestion mode" edits are ops tagged as proposed — applied to a separate layer until accepted, when they become normal ops. Same op-log + convergence machinery, different op types.

  5. What’s the consistency model — is everyone always identical?

    Eventually consistent, converging fast. At any instant two editors may briefly differ (an op in flight), but OT/CRDT guarantees that once all ops are delivered every copy is identical — convergence, not lockstep. Applying your op locally immediately and reconciling when the server’s ordering arrives is what makes it feel instant.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Edits are sent as tiny operations (not whole-doc saves) because…

    • It’s less code
    • Ops preserve intent and are cheap to order and merge
    • It’s more secure

    Snapshots clobber each other and lose what changed; the op stream is the source of truth.

  2. OT and CRDTs both guarantee…

    • Lower latency
    • Convergence — same ops, any order, same document
    • Encryption

    They reconcile concurrent edits so every copy ends identical.

  3. Live updates reach other editors via…

    • Polling
    • Op broadcast over WebSockets (pub/sub across servers)
    • Email

    Real-time means push; pub/sub relays an op to whichever server holds each connection.

  4. Fast loads + durability come from…

    • Saving the whole doc each keystroke
    • A durable op log + periodic snapshots
    • Keeping it only in memory

    Log is the truth; snapshots are checkpoints — load latest, replay ops since.

  5. Offline edits merge on reconnect because the client…

    • Overwrites the server copy
    • Tracks its last version and pulls/transforms ops since
    • Blocks until online

    Versioned op logs make sync "ops after N, transformed against mine" — offline is delayed real-time.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Co-edit: many people edit one doc at once and all see the same text.
  • Send ops: each change is a tiny insert/delete at a position, not a whole-doc save.
  • Broadcast: an accepted op appears on every other editor’s screen in well under a second.
  • Persist: never lose a keystroke, and still load a long doc fast.
  • Presence & permissions: show live cursors, and let only editors edit.

The qualities that shape everything

Each one names the mechanism that buys it.

Every copy ends identical
OT transforms an incoming op against ops it didn’t see, or CRDTs give each char a stable id — both guarantee convergence: same ops, any order, same doc.
Sub-second live updates
Push each accepted op over WebSockets to every other editor, with pub/sub relaying across servers — real-time is push, not poll.
Never lose work, still load fast
Append every op to a durable op log (the source of truth) and write periodic snapshots, so recovery loads the latest snapshot and replays only the ops since.
A cursor flood can’t risk the doc
Run presence — who’s here, each cursor/selection — on a separate ephemeral path that is broadcast constantly but never persisted to the op log.
Edit offline, merge cleanly on reconnect
Each client tracks its last-seen version; on reconnect it sends buffered ops and pulls ops since, and OT/CRDT merges the two streams — offline is delayed real-time.
Only editors edit, and it scales
Check permissions on join and on every op server-side, and shard by document so all editors of a doc share one authoritative server — no consensus per keystroke.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Tiny insert/delete ops over whole-document saves

Whole-doc saves clobber each other — last write wins — and throw away what changed, so intent can’t be merged. Small ops preserve intent, are cheap to order and merge, and the op stream becomes the source of truth.

OT / CRDTs over a global edit lock

A lock is just "file is locked" again — it kills real-time collaboration. Transforming or merging concurrent ops lets everyone type at once and still converge: same ops, any order, same document.

WebSocket push over polling for changes

Polling every second is laggy and mostly returns nothing; collaboration needs sub-second updates. The server pushes each accepted op the moment it lands, with pub/sub relaying across servers.

Op log + periodic snapshots over saving the whole doc per keystroke

Rewriting the full doc every keystroke is massive write amplification and still loses concurrent intent. An append-only log is the truth; snapshots checkpoint it so loads replay only the ops since.

Ephemeral presence path over cursor moves in the op log

Persisting a flood of high-frequency cursor updates would swamp the durable log with data nobody replays. Presence is throwaway — broadcast but never stored — so a cursor storm can’t risk the document.

What this teaches

Learn system design by building a real-time collaborative editor like Google Docs step by step. An interactive guide covering the document model, conflict resolution with OT and CRDTs, op broadcast over WebSockets, persistence with an op log and snapshots, presence, offline sync, and access control.

Key takeaways

  • Edits are tiny operations on an ordered model, not whole-document saves.
  • OT or CRDTs resolve concurrent edits so every copy converges.
  • Accepted ops broadcast over WebSockets (and pub/sub) for live collaboration.
  • A durable op log plus periodic snapshots give recovery, history and fast loads.
  • Presence (cursors, who’s here) runs on a separate, ephemeral path.
  • Versioned op logs make offline editing and reconnection sync just work.
  • Server-side permissions and per-document sharding handle security and scale.

Concepts covered

  • What is a collaborative editor?
  • Edits are operations
  • Resolving concurrent edits
  • Broadcast to everyone
  • Persist: op log + snapshots
  • Presence & cursors
  • Offline & reconnection
  • The sharp edges
built to be co-edited, not memorized — make the calls, cut the broadcast, run the gauntlet.
Finished this one? 0 / 65 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs