Skip to content

03. Collaborative Editor — Interview Q&A

~16 min read · Part 4 of 4 (Overview → HLD → LLD → Q&A)

These are the questions an interviewer actually asks once the boxes are on the board. Each answer is the strong version, followed by the wrong answer that quietly sinks candidates.

Q1. OT or CRDT — which do you use for a Google-Docs-style editor, and why? For a server-hosted cloud editor, OT with a central authority. The deciding factor is that you already have a server in the loop for persistence and fan-out, so OT's one requirement — a central serializer — is free, and in exchange you get tiny ops (a position and a character), a compact op-log, and a single codebase where the transform correctness lives and can be tested. There is a real theoretical dividend too: because the server imposes one total order, the transform only has to satisfy TP1 and never TP2, the transform-of-transforms property that makes peer-to-peer OT so hard to implement correctly. CRDTs are the right call when there is no reliable central server — local-first apps, true peer-to-peer, offline-heavy collaboration like Figma's canvas — but they pay for that with per-character id metadata and delete tombstones that inflate memory well past the visible text. Common wrong answer to avoid: "CRDTs are strictly newer and better, so always use them." CRDTs solve the serverless convergence problem; when you have a server, you pay CRDT's metadata cost for a coordination guarantee you already have for free.

Q2. When a user types, do you send the keystroke to the server and wait before showing it? No — the client applies every edit optimistically, rendering it locally in ~0 ms, then reconciles with the server afterward. Waiting for a round-trip (30–60 ms) before showing your own text makes typing feel laggy and unusable. The client keeps a pending queue of edits it has applied locally but not yet had acknowledged; when the server's ordered ops arrive, the client transforms them against that pending queue so the two never corrupt each other, and pops each pending op on its ack. The user is briefly, invisibly "ahead" of the server, and the transform heals the difference. Common wrong answer to avoid: "Send to server, get the authoritative state back, then render." That is a synchronous round-trip per keystroke — every character lags by the network latency, which is exactly the experience collaborative editing must avoid.

Q3. Fifty people type into the same paragraph at once — how does every screen end up showing identical text? Every edit routes to that document's single session server, which lays all 250 ops/second into one total order, assigning each a monotonic revision and transforming it against precisely the ops it hadn't seen. When several editors insert at the same offset in the same instant, the transform breaks the tie deterministically by client id — a total rule every machine computes identically — so the characters land in one agreed order, say A, B, C. Every client then replays that same total order through the same transform and reproduces the identical string. Convergence comes from the total order plus a transform satisfying TP1 (applying a then transformed b equals b then transformed a), not from timing or luck. Common wrong answer to avoid: "Whoever's edit arrives last wins that position." Last-writer-wins silently drops edits the users watched themselves type; the whole point is that all 50 edits survive in one agreed order, not that 49 get overwritten.

Q4. Do you store the full document or the stream of edits? How do you make opening a doc fast? Both, because each is cheap where the other is expensive. The durable op-log is the system of record — an append-only sequence of ops per document, which also gives you undo and audit — but replaying a million ops to open a document costs ~2 seconds even in memory, far too slow. So materialize a snapshot of the full text every ~1,000 ops; a cold open loads the latest snapshot (~100 KB) and replays only the short tail since, roughly 2 ms. Writes stay cheap appends; reads stay cheap snapshot-plus-tail. The snapshot is derived from the log, so it can never disagree with it, only lag. Common wrong answer to avoid: "Just save the current document text on every edit." Overwriting the whole document per keystroke is a huge write amplification, destroys the edit history undo and reconciliation need, and still doesn't solve concurrent merge.

Q5. The session server owning a hot document crashes mid-edit. What happens? The in-memory document and transform state are lost, but nothing acknowledged is lost, because an edit is appended to the durable op-log before its ack is sent. A replacement server acquires the document's lease, loads the latest snapshot, replays the op-log tail to rebuild the exact authoritative text and current revision, and resumes. Clients reconnect with their last-acked revision and resend unacknowledged pending ops; the server transforms those forward, and the (client_id, client_seq) idempotency key means any op that was actually committed before the crash is recognized and re-acked rather than double-applied. Common wrong answer to avoid: "Recover the document from the last snapshot." The snapshot alone loses every edit since it was taken; recovery must replay the op-log tail on top, and clients must resend their un-acked pending ops.

Q6. How do you keep two servers from both owning one document? A single-writer lease on the document id from a consensus store (etcd/Chubby-style). A session server accepts edits only while it holds a live, unexpired lease, and every op-log append carries a fencing token that the log rejects if it comes from a server that no longer owns the document. Two owners would produce two independent total orders — the one unrecoverable failure in this design, because divergent orders can never be reconciled — so the system is built to stop accepting edits on any doubt about ownership rather than risk a second order. Common wrong answer to avoid: "Use a load balancer that pins the document to a server by hashing." Consistent hashing routes traffic but does not guarantee single ownership across a rebalance, a network partition, or a slow-to-die process; you need a lease with fencing, not just routing.

Q7. Could you scale one document by sharding its edits across two servers? No, and recognizing why is the point. A single document is a single serialization point: convergence depends on there being exactly one total order, and splitting its edits across two servers creates two orders that diverge. That is the hard ceiling. So you scale the number of documents by sharding on document id across the fleet, but within one document you scale by bounding concurrent editors — real editors cap simultaneous editors around ~100 — and pushing the long tail of participants onto read-only replicas fed by the same op stream. Fifty editors is ~12,250 fan-out messages/second, fine for one owner; five thousand editors would be ~125,000,000 messages/second, which forces the editor/viewer split. Common wrong answer to avoid: "Shard the document by paragraph or by section across servers." Cross-shard edits (a selection spanning two sections, text moving between them) reintroduce exactly the multi-order convergence problem you were trying to escape, now with cross-shard coordination on top.

Q8. What happens when a client goes offline for a bit and keeps typing? It keeps editing locally against its pending queue — optimistic application means offline editing is just the online path without acks arriving. On reconnect the client replays its queued ops with their original base_rev, and the server transforms each forward against everything committed during the gap. For a short outage that reconciles cleanly. If the gap is very large, transforming against a huge concurrent list gets expensive, so the cheaper path is to discard the optimistic local state, reload a fresh snapshot, and re-apply the user's pending edits as new ops against the current revision — bounded work instead of an unbounded transform chain. Common wrong answer to avoid: "Block editing until the connection is back." That defeats offline tolerance; the design's optimistic model already handles bounded offline windows, and a hard block would be a worse experience than the reconciliation it avoids.

Q9. Why does a central server make OT dramatically easier than peer-to-peer OT? Because the server defines a single total order, every incoming op only ever transforms against a linear, already-ordered list of ops — so the transform function only needs to satisfy TP1 (two concurrent ops converge). Peer-to-peer OT, with no agreed order, additionally requires TP2, the property that transforming through different sequences of concurrent ops yields the same result, and TP2 is famously hard — several published OT algorithms were later shown to violate it. Centralizing the order deletes the hardest correctness obligation in OT, which is a large part of why Google Docs is server-authoritative rather than peer-to-peer. Common wrong answer to avoid: "OT is OT; the server is just for storage." The server is not incidental — it is what reduces the correctness burden from TP1+TP2 to TP1 alone, which is the difference between a transform you can ship and one that is a research problem.

Q10. How do live cursors stay in the right place while everyone is typing? A cursor is an offset into text other people are changing, so every applied op transforms the live cursor offsets with the same shift rule as inserts and deletes: text inserted before a cursor pushes it right, text deleted before it pulls it left. Without that transform a remote cursor visibly slides to the wrong character during concurrent typing. Cursor and selection updates ride the ephemeral pub-sub with a ~2 s heartbeat and ~10 s TTL, so a departed collaborator's cursor ages out on its own, and presence is deliberately lossy — under backpressure the gateway drops cursor frames to a slow client before it ever drops an edit. Common wrong answer to avoid: "Store cursor positions in the database alongside the text." Cursors are ephemeral, change many times a second, and must never share the durable path with edits; persisting them adds write load and risks presence stalling real editing.

Q11. One of the 50 editors is on a slow laptop that can't keep up with the fan-out. What breaks, and what do you do? The risk is not correctness — the total order still converges — it is fan-out backpressure: at ~12,250 broadcast messages/second, a client that can't drain its socket builds a growing send buffer on the gateway. The lever is per-connection backpressure with a priority order: coalesce or drop that client's presence updates first, because a skipped cursor frame is invisible, and only if edits still can't drain do you disconnect that one client and force it to resync from a fresh snapshot. The invariant is that one slow client can degrade only itself, never stall the other 49. Common wrong answer to avoid: "Slow down the whole document to the pace of the slowest client." Coupling everyone to the slowest connection turns one weak laptop into a document-wide stall; isolate and resync the slow client instead.

Q12. How do you handle undo when several people are editing? Undo must be local and intention-preserving: undoing your own last edit should remove your change, not blindly revert whatever the latest edit to the document was (which might be a colleague's). Because the op-log records each op with its client_id, undo is implemented as generating an inverse op for the user's own most recent edit and running it through the same transform pipeline against everything committed since — so it applies correctly even though other people have edited around it. It is a new op in the total order, not a rewind of the revision counter. Common wrong answer to avoid: "Undo just decrements the revision number to the previous state." That reverts everyone's edits since, not just yours, and corrupts the shared document; undo is a forward inverse op, not a global rewind.

Deeper follow-ups

  • How would you extend the plain-text op model to rich formatting (bold, links) without changing the transform engine — and why does modeling attributes as Retain-with-attrs ops keep the machinery intact?
  • Where exactly does this design break if you tried to run it truly peer-to-peer with no central server, and which single property (TP2) is the reason?
  • How would you support 100,000 viewers on a live document (a broadcast during a keynote) while keeping the ~100-editor cap — what does the read-replica fan-out tree look like?
  • How do you migrate the op-log format or transform logic across a fleet without a version skew that lets an old server and a new server disagree on transform results?
  • How would you bound op-log growth for a document edited continuously for years — compaction, snapshot cadence, and what you lose if you truncate history?
  • If low-latency editing were required across continents, where would you place the document authority, and what would you accept giving up (a single global order vs. regional latency)?

How this round is scored

Interviewers use the collaborative editor to see whether you treat convergence as the core problem and latency as the constraint that shapes the solution, rather than reaching for locks or last-writer-wins. The strongest early signal is naming the OT-versus-CRDT decision and resolving it with a reason tied to having a server — including the TP1-versus-TP2 insight, which separates candidates who have implemented sync from those who have read about it. Seniority shows in the optimistic-local-application model (0 ms feedback, reconcile after), in the snapshot-plus-op-log storage split with the replay math behind it, and in knowing that one document is a single serialization point that cannot be sharded. The failure-mode discussion — session-server crash and durable replay, split-brain and leases, the slow client under fan-out backpressure — is where you show you have run these systems, not just drawn them. Walking the 50-editor scenario through the transform and landing on byte-identical text, out loud, is what pushes an answer from "correct" to "senior."