InterviewLegend

Design Real-Time Multiplayer Editing

Signature problem
Alex Chen45 min readUpdated 17 real interview reports
In this lesson
  • Frame multiplayer editing as convergence: every client lands on the same document, whatever the edit order
  • Build the live loop: one server process per document, with a property-level last-writer-wins register
  • Explain why Figma rejected both operational transformation and pure CRDTs

Understanding the problem

The prompt

Design the backend for Figma-style multiplayer editing. Many people edit one design file at the same time, and everyone sees each other's changes within a fraction of a second. Concurrent edits to the same object resolve cleanly, and a client that goes offline can keep working and reconcile when it reconnects.

This is the flagship of the Figma design round, and it is the one prompt where the system you would build on the job shows up almost unchanged. A Figma file is not a document one person opens and saves. It is a live canvas a whole team edits together, the way a design review works when six people drag shapes around the same frame at once. Figma has written about the real engine behind this in unusual depth, so the interviewer often knows exactly how it behaves. A vague "everyone edits and it syncs with a CRDT" answer gets taken apart quickly, because that is not what Figma built. Start with what makes this hard. The moment two people change the same thing at the same instant, someone has to decide what the document now says, and every client has to end up agreeing.

The Figma angle
Say plainly that Figma uses neither operational transformation nor a pure CRDT, because a Figma interviewer built the real thing and the popular prep guides get this wrong. Figma's own engineering writing is direct about it. Operational transformation was judged too complex for the problem, and pure CRDTs are built for decentralized peer-to-peer systems, so they carry coordination overhead Figma does not need. Figma has one authoritative server per document, which means that server can simply define the order of events. The conflict model that falls out is a last-writer-wins register kept per property, with no timestamp required, because the server's order is the order. Leading with that, and being able to explain why Figma rejected both alternatives, is the single strongest signal you can send in this round. Parroting "use a CRDT" is the trap.

Functional requirements

Keep the surface small. Let many people edit one file together in real time, make concurrent edits converge to one agreed document, and let a client keep working offline and reconcile on reconnect. Everything else hangs off those three.

In scope

  • Concurrent editing: many clients apply edits to one document at once, and every edit reaches the others within a fraction of a second
  • Convergence: whatever order concurrent edits arrive in, every client ends on an identical object tree, with a clear rule for two edits that touch the same property
  • Offline and reconnect: a client that drops keeps editing locally, then reconciles when the socket returns
  • The document model: a tree of objects with stable identity, correct child ordering, and safe reparenting under concurrent moves
  • Undo and redo per client, so local history stays coherent while other people are editing the same file

Below the line (name it, then skip)

  • Live cursors and presence (who is here and where their cursor is): a separate ephemeral channel, designed in its own lesson
  • Comments, notifications, and file listings: these are not document edits; they live in Postgres and sync through a different real-time layer
  • The rendering engine that draws the canvas at 60fps: the client paints the tree, and that is its own frontend design
  • The rich vector editor UI and tools: we design the sync layer under the editor, not the editor itself

Non-functional requirements

Put a number on each one. Latency is what the product lives or dies on. The gap between one person moving a shape and everyone else seeing it move should stay under about a tenth of a second in-region. Your own edit has to paint instantly, so the canvas never waits on the network to show your own drag. Convergence is the correctness bar and it is absolute. Given the same set of edits, every client must reach an identical object tree, and the rule for a clash on one property must be defined, not accidental. Availability matters more than strict global consistency. A file that briefly reorders and heals is fine; a file that stops accepting edits is not. Durability applies to accepted edits and to saved versions. Once the server has taken an edit it must survive a crash, and a file must be recoverable. Scale has an unusual shape worth stating early. The load on any single document is small and human-bounded, but there are a very large number of documents, and each live one needs a single point that decides edit order.

  • Latency: Under ~100 ms. Edit to remote paint, same region. Your own drag paints locally with zero network wait; the budget covers the trip to the document process and the fan-out back out.
  • Correctness: Convergence, defined. Same edits in, identical object tree out on every client. A clash on one property resolves by last-writer-wins under server order, never a silent blend.
  • Scale shape: Many docs, light each. A hot file is tens of editors and a few hundred ops a second, trivial for one machine. The axis that grows is the count of open documents, each owning one order-deciding process.
  • Durability: Accepted edits + versions. An acknowledged edit survives a crash via the op journal; every saved version is recoverable via snapshots. Recovery replays a bounded tail, never the whole history.

The set-up

Core entities

Name the objects before drawing. Five carry the design, and the operation is the one that does the work.

1

Object

A node in the document tree: a frame, a shape, a text layer, a component instance. It has a client-generated ID and a set of properties. Its parent is stored as one of its own properties (a link), not as a children list on the parent, which keeps identity stable when two people move it at once.

2

Document

The whole file as a tree of objects, much like the browser DOM: a root, page objects, then a hierarchy of content. Conceptually it is a two-level map, Map>. One authoritative server process owns it, holds it in memory, and orders every change to it.

3

Operation

One atomic edit: set property P on object O to value V. This is the unit that travels over the wire between clients, and it is the currency of the whole system. Convergence is a statement about operations, which is why they, not whole documents, move around.

4

Property register

For each (object, property) pair, the latest value the server has accepted, in the server's order. This is the last-writer-wins register at the heart of the conflict model. No timestamp is needed, because the single server already defines what came last.

5

Session

One client's live WebSocket to that document's server process, carrying the participant's identity and the per-client unique prefix it uses to mint object IDs while offline. When the socket ends, the client stops contributing edits until it reconnects.

The Figma angle
Keep the document-sync path separate from everything that is not a document edit, because Figma does exactly that and the split is a senior signal. The multiplayer system syncs document objects and nothing else. Comments, notifications, file listings, users, and teams live in Postgres and reach clients through a different real-time layer that tails the database's replication log. So a cursor position is ephemeral and lossy, a document edit is durable and ordered through the per-document process, and a comment is relational data surfaced by a live read subscription. Putting each kind of data on the consistency regime it actually needs, instead of forcing all of it down one channel, is the reasoning most candidates skip. In this lesson we design only the durable, ordered edit path; the ephemeral and relational paths are their own designs, and knowing where the boundary sits is part of the answer.

API

The interface is a persistent WebSocket, not a set of one-shot REST calls, because edits flow both ways continuously. A client opens a session on a file, streams operations up, and receives other people's operations back down. A couple of thin calls handle the join and the reconnect, and object creation gets its own shape because IDs are minted on the client.

The interface
RequestServer push
Editor clientsync gatewayopen a live editing session on a file
WS connect /doc/{fileKey}
the current object map + version + the participant list
the gateway routes the socket to the one server process that owns this document
Editor clientdocument serverset one property on one object
send({ objectId, property, value })
ack once the change is taken into the server order and the register is updated
document serveropen sessionsbroadcast an accepted property change to everyone else
push({ objectId, property, value })
applied by each client to converge on the same tree
Editor clientdocument servercreate an object, including while offline
create({ clientObjectId, parentId, position })
the ID is minted on the client so two clients never collide
Editor clientdocument serverreconnect after a dropped connection
resync()
a fresh copy of the document; the client replays its buffered offline edits on top

Two details in that interface carry the design. First, a property change names a single object and a single property, never a range of text or a batch of shapes, because the register resolves at exactly that grain. Second, create mints its ID on the client rather than asking the server for one. A person can add shapes on a plane with no signal and never collide with anyone else's IDs when they reconnect. Cursors and selections are not in this interface at all; they ride a separate throwaway channel covered in the presence design.

High-level design

Draw the live loop and the one component that makes it correct. Editor clients hold WebSocket sessions to a sync gateway, and the gateway routes each session to the single document server that owns that file. That server holds the object map in memory and takes every incoming operation into a defined order. It updates the property register, appends the operation to a durable journal, snapshots periodically, and pushes each accepted operation back out to the other sessions. The piece everything hinges on is that one authoritative process per document, because a single point deciding order is what makes convergence something you can reason about instead of merge after the fact.

Color marks the role. Blue is the editor clients, the people dragging shapes. Violet is the sync gateway and the document server. Green is the op journal and the snapshot store. Amber is the broadcast fan-out back to the open sessions.

Loading diagram...

1) Join a session. A client opens a WebSocket to the sync gateway with the file key. The gateway routes it to the document server that owns that file, spinning the process up if the file is not already live. The server replies with the current object map and its version. The client materializes the whole tree in one round trip and starts painting it. A busy file has maybe fifty participants, so this is a light join. The open question it raises is how a file gets pinned to exactly one process, and what happens when that process dies, which the coordinator deep dive picks up. For now, the important thing is that from this point on there is a single place that owns this document's truth.

2) Make an edit. When you drag a shape, the client changes its local copy immediately, so your own move paints with zero network wait. Then it sends an operation up naming the object, the property, and the new value. The document server is the single writer. It takes the operation into its order, sets that property's register to your value, and appends the operation to the journal. There is no transform step here. There is no timestamp comparison either, because the server order already settles who came last. That is the entire conflict resolution, done in one place at the grain of a single property. The operation is durable the moment the journal has it.

3) Broadcast and converge. The server pushes each accepted operation, with its place in the order, to every other open session, and each client applies it. Because one server decides the order, the sequence every client applies is identical, which is what makes them converge. There is one subtlety worth stating. If an incoming server update conflicts with a local change you have made but the server has not acknowledged yet, the client keeps its own value. That local change is its best prediction of the latest edit in server order. When your operation is acknowledged, everyone lands on the same value. The numbers here stay small, which the sizing section works out.

4) Persist, snapshot, and reconnect. Every accepted operation is already durable in the journal, so a crash loses nothing acknowledged. Periodically the server writes a full snapshot of the object map, which bounds how much the journal has to replay on file-open or recovery to a short tail. When a client drops, it keeps editing its local copy and buffers the operations it could not send. On reconnect it downloads a fresh copy of the document and reapplies its buffered edits on top, then resumes over a new socket. That download-fresh-and-reapply flow is Figma's real reconnect behavior, and it is the subject of its own deep dive.

The Figma angle
Anchor the whole design on one authoritative process per document, because that is the real Figma topology and it bounds the entire problem. Every client editing a given file connects over a WebSocket to a single server process that owns that document, written in Rust for throughput. That one process holds the current object map in memory, takes every operation into an order, and stamps the register. Convergence becomes easy to reason about once you say this: there is exactly one place that decides what happened first, so there is nothing left to merge after the fact. The pressure then moves to what a single process implies, its memory ceiling on a giant file, what happens when it dies, and how a file gets pinned to one process and only one. Naming the coordinator early is what sets up those deep dives instead of scrambling for them when the interviewer pushes.

Sizing one document's sync load

The arithmetic is worth doing, because it changes what you optimize. Take a hot but realistic file. Fifty participants are present, and maybe ten are actively dragging at a given instant. Dragging a shape emits a stream of property updates as the shape moves, coalesced to something like thirty updates a second per active dragger rather than one per rendered frame. Ten active draggers is then on the order of three hundred operations a second on the busiest file. Each accepted operation fans out to the other forty-nine sessions, so the fan-out is roughly 300 times 49, about fifteen thousand messages a second. That is nothing for one machine. The per-document load stays small no matter how popular Figma gets, because a file has a human number of editors, not a viral one.

Memory is set by the file, not the editor count, because the process holds the whole object map resident. A heavy production file might hold a hundred thousand objects, each a small map of a parent link, a position, and a dozen visual properties. That lands around a hundred megabytes in memory for a large file and far less for an ordinary one. So the axis that actually grows is the number of files open at once. Say a large deployment has three hundred thousand files being co-edited at peak, averaging three participants each: that is close to nine hundred thousand live WebSocket connections. One connection server holds tens of thousands of sockets, so the gateway fleet is a few dozen machines. Documents are sharded across the server fleet so that all sessions for a file share one process. The design scales by adding servers and spreading files across them, never by making a single file faster.

  • Concurrent editors: ~50 per hot doc. Fifty present, ~10 actively dragging at once. Design reviews and classrooms push into the hundreds, which is the coordinator deep dive; most files are a handful.
  • Peak ops/sec: ~300 per doc. ~10 active draggers emitting ~30 coalesced property updates a second each. Fan-out is ~15,000 messages/sec to the other sessions, trivial for one machine.
  • Sync latency: Under ~100 ms. Edit to remote paint, same region. Your own edit echoes locally with no wait; the budget covers the trip to the document process and back out.
  • Doc in memory: ~100 MB heavy file. ~100k objects, each a small property map, held resident by its process. Ordinary files are far smaller. This ceiling drives the memory-bound deep dive.

Technology choices

Each box in the diagram is a real category of tool. For every one, name a concrete option, say why that kind of tool fits, and have the failure mode ready, because this round piles on scale and failure the moment you sound settled.

  • The sync gateway terminates a WebSocket per editor, because edits flow both ways continuously and reopening an HTTP request per drag frame would add a round trip to every movement. The socket also carries auth once, not per message. The failure to have an answer for is the network that blocks WebSockets: fall back to XHR long-polling so editing degrades to slightly laggier rather than failing. A sticky or consistent-hash route keeps a file's sessions on the gateway that already knows which document server owns it.
  • The document server is a stateful in-memory service, one process per file (Figma writes these in Rust), holding the object map and the property registers in RAM. It is deliberately not a stateless pool behind a load balancer, because ordering concurrent edits needs a single decision-maker per file. Spread that across replicas and you are back to merging conflicts after the fact, which this design avoids. The failure probe is the obvious one: the process dies. Recovery is rebuild-from-journal, load the latest snapshot and replay the tail onto a fresh process, re-pin the file there, and have clients reconnect and reapply. Pinning uses a consistent-hash or lease so exactly one process owns a file at a time.
  • The op journal is an append-only, ordered, durable log (a Kafka-style partitioned log keyed by file, or an append-only table keyed by file and sequence). You reach for a log rather than a mutable row because the ordered sequence of operations is the source of truth. An ordered durable sequence is exactly what a log gives you: replayable for recovery, tailing for late joiners, immutable for history. The failure to cover is durability of an acknowledged edit, answered by replicating the log before the server acks; an operation is only accepted once the log holds it on enough replicas. Snapshots keep the live tail short.
  • The snapshot store keeps full object-map snapshots and saved versions in object storage (S3-style) with metadata and version pointers in Postgres. Object storage fits because a snapshot is a large write-once blob a database would buckle under, while Postgres fits the small structured version metadata you query and list. The probe here is history growth, since a file accumulates endless operations. So you snapshot on a cadence and keep periodic named versions rather than every property change forever, trading fine-grained history depth for bounded storage. Restore falls out of snapshot plus a replayed tail.
  • The broadcast fan-out is in-process with the document server, pushing each accepted operation to the file's other sockets. The server already holds the live session set, and the fan-out is tiny (thousands of messages a second on the hottest file). There is no need for an external pub/sub on the edit path when one process owns every session for a file. The failure mode is a slow or wedged client: apply backpressure per socket and, past a threshold, drop that client to a reconnect rather than letting one stuck consumer stall the loop for everyone.

Two of these choices carry the design. The single-writer document server is what makes convergence tractable at all. It is also the availability risk, so its recovery story has to be crisp. The op journal is the source of truth that lets a crashed process come back exactly where it left off. The rest are well-understood parts. The gateway, the snapshot store, and the in-process fan-out sit around one idea: order every edit through a single point per file, make that order durable, and stream it out fast.

Potential deep dives

This is the part of the round that separates candidates. The Figma version ladders on purpose. The interviewer adds editors, drops a connection, crashes a process, then asks what actually happens when two people edit the same shape. Four threads carry the design. How the conflict model really resolves an edit. How the document tree keeps stable identity and order under concurrent moves. What happens when a client goes offline and comes back. And what the one-process-per-file model implies for memory, hot files, and recovery.

1) The conflict model: last-writer-wins register versus OT and CRDTs

Everything comes down to one moment. Two people change the same object at the same instant. Both operations reach the server. The system has to fold both in so every client ends up identical and the rule for the clash is defined, not luck. This is the decision the round is built around, so reason it out rather than naming an algorithm.

BadLast-write-wins on whole-document saves

Approach

Each client periodically sends its full document; the server keeps whichever arrived last.

Challenges

Whoever saves second overwrites everything the other person did since the last save, so a coworker's new frame vanishes while they are still working on it. That is the exact failure the product exists to prevent. It also ships the entire file constantly, so it is both wrong and heavy. Nobody should propose this, but naming it shows why the better answer works at the grain of a single property, not a whole document.

GoodA pure CRDT: order-independent merge

Approach

Give every object and character a globally unique, ordered identifier so concurrent edits merge to the same result no matter what order clients apply them, with no central coordinator needed. Each operation commutes with the others, so peers can sync directly and still converge. This is what makes offline-heavy and truly peer-to-peer editing natural, and it is where a lot of newer collaborative-editor work has gone.

Challenges

The convergence comes without a central authority, but you pay for it. Every unit carries identity metadata, so the in-memory footprint grows well past the visible content, and deleted units leave tombstones that need compaction. That machinery exists to solve decentralized peer-to-peer sync, and Figma is not decentralized: it already has one server per file that can define the order. So a full CRDT is more overhead than this problem needs, which is exactly the reasoning Figma published for skipping it. It earns its keep when you genuinely have no central coordinator, not here.

GreatServer-ordered last-writer-wins register, per property

Approach

Model the document as a set of (object, property, value) tuples. The one server per file takes every operation into a single order, and for each property it simply keeps the latest value in that order. There is no transform and no timestamp, because the server order already answers who came last. A clash on one property resolves to one client's full value. This is Figma's actual choice, and it works precisely because the central server removes the need for the coordination that OT and CRDTs are built to provide.

Challenges

The cost is that the server is a single writer per file, so it is a scaling and availability chokepoint you have to plan recovery around, which is the coordinator deep dive. And resolution is whole-value per property, so two people editing the same text layer at the same instant do not get a character-level merge. One full value wins and the other is dropped on that property. That is an accepted tradeoff for a design canvas, where properties are small and independent, but you have to name it rather than pretend edits always survive.

Commit to the register and defend it against the interviewer's push. For a central-server design canvas, server-ordered last-writer-wins at the property grain is simpler and lighter than operational transformation and carries none of a CRDT's per-unit metadata, and it is what Figma runs. When the interviewer ladders into offline-first or genuinely peer-to-peer or hard multi-region, that is your cue to name where a CRDT's order-independence would start to earn its metadata cost. Reasoning about when you would switch is worth more than defending one model to the death.

The Figma angle
Make the property-level atomicity boundary explicit, because it is a real Figma decision and a spot the round probes. A Figma edit resolves at the grain of one object property. The converged value of a rectangle's fill is always exactly one client's value, never a blended average, and the same holds for its position or its text content. When the interviewer asks what happens on a simultaneous edit to one text layer, the honest answer is that last-writer-wins can drop the loser's change on that property. That is accepted because a design file's properties are small and independent, not a shared paragraph being co-typed. Figma also runs these rounds inside Figma or FigJam and grades how you think with them, so state that tradeoff and invite the pushback instead of presenting a sealed answer. A candidate who narrates the reasoning scores better than one who silently lands on the same design.

2) The document tree: stable IDs, child order, and safe reparenting

The register settles a single property, but a design file is a tree, and trees have their own concurrency hazards. Two people create objects offline at once. Two people drop a shape into the same spot in a list. Two people reparent objects into each other. Figma solves each of these with a specific, quotable choice, and walking them is what turns a generic answer into a Figma one.

Start with identity. New object IDs are generated on the client, not the server. Each embeds a per-client unique prefix, so two clients creating shapes at the same instant, even both offline, can never mint the same ID. This is why creation works with no connection at all. The server does not need to hand out IDs, so a person on a plane keeps building, and their objects slot in cleanly on reconnect. Deletion is the mirror image. The server stores nothing for a deleted object, and its properties live in the undo buffer of the client that deleted it, so an undo brings it back with no server-side tombstones to keep.

BadChild order as an integer index array on the parent

Approach

Store each parent's children as an ordered list of IDs, and reorder by rewriting the list.

Challenges

Two people inserting a shape at the same position both rewrite the same list against a stale view, so one insert clobbers the other. And every insert in the middle renumbers everything after it, a large write for a small change. Worse, the list lives on the parent, so moving an object touches two parents at once and races badly. This is the tree version of whole-document save: it resolves order by overwriting rather than by a rule.

GreatFractional indexing, with parent and position as one property

Approach

Give each object a position that is a fraction between 0 and 1, and store the parent link and that position together as a single property on the object. To insert between two siblings, take a value between their two positions, roughly the average, so the insert touches only the moved object and needs no renumbering. Because parent and position are one property, a move updates atomically under the same last-writer-wins register, so an object can never be seen with a new parent but an old position.

Challenges

Repeatedly inserting between the same two neighbors shrinks the gap and eventually exhausts floating-point precision, so you need a scheme that can extend precision or occasionally rebalance a run of siblings. And two people can still both pick a position between the same pair, landing two objects at nearly the same fraction. The register breaks the tie deterministically by object ID, so every client orders them the same way. These are small, bounded problems next to renumbering a shared list on every insert.

The last hazard is reparenting into a cycle: two people each drag object A into B and B into A at the same time, which would make the tree eat itself. Because parent is just a property, the server checks each parent update and rejects the one that would create a cycle. Clients handle the brief window optimistically by temporarily parenting such objects to each other and pulling them out of the visible tree until the server confirms which move survives. The result is that the tree always stays a tree, with no multi-parent or orphan states, even while several people rearrange it at once. Naming the cycle check specifically is one of the sharper Figma-specific details you can bring.

3) Going offline and coming back

Connections drop constantly: a laptop sleeps, wifi flickers, someone edits on a train through a tunnel. Figma's promise is that you keep designing through it and nothing local is lost when you return. That makes the client part of the sync story, not just the server, and Figma's real reconnect flow is simpler than the machinery people reach for.

While offline, the client keeps applying edits to its local copy and buffers the operations it could not send. Meanwhile other people have kept editing, so the document the client last saw is stale. When the socket returns, the client has a choice about those buffered operations.

BadReplay buffered ops blindly on reconnect

Approach

Send the buffered operations up as-is, against the version the client last saw, as if no time had passed.

Challenges

The trouble is that some of those operations reference state that changed underneath them while the client could not see it. There is no server-ordered acknowledgment for them yet. So replaying them naively can resurrect a value someone else already changed, or apply a move against a parent that no longer exists. The client and everyone else can drift apart. This is the offline version of trusting a stale view over current reality.

GreatDownload a fresh copy, then reapply the offline edits on top

Approach

On reconnect the client downloads a fresh, current copy of the document from its server process. It then reapplies its buffered offline operations on top of that current state and resumes syncing over the new socket. This is exactly what Figma does. It works cleanly because operations are per-property register sets, so reapplying them onto current state is well defined. Each becomes the latest writer for its property in the new server order. Object IDs minted offline are already collision-free, so offline-created objects simply appear.

Challenges

The honest cost is that a property someone else changed while you were gone resolves to whichever of you the server orders last. A long offline session can therefore lose an edit on a contested property, the same last-writer-wins tradeoff as live editing, just spread over more time. Show a reconnecting state so the user knows their edits are being reconciled. For a very long or very large divergence, prefer reloading fresh over quietly reapplying a huge buffer that would read oddly next to what changed.

The clean summary ties back to the entity model. Because edits are per-property operations against a server-ordered register, and object IDs are minted on the client, reconnect is a bounded, mechanical reapply rather than a merge negotiation. That is the payoff for making operations, not whole documents, the unit of the system, and for pushing ID generation to the client so offline creation is a first-class case rather than an afterthought.

4) The one-process-per-file coordinator: hot files, memory, and crash recovery

The single authoritative process is what makes convergence easy, and it is also the design's soft spot, so the interviewer will lean on it. Three pressures matter: what happens when far too many people join one file, what bounds a process's memory, and what happens when a process dies.

Take the hot file first. Ordinary files have a handful of editors. A company all-hands design review or a classroom can put hundreds of people on one document. The fan-out is still cheap in message count, but a few hundred sockets on one process, each dragging, pushes CPU and bandwidth on that single machine. Keep the ordering authority single while you spread the fan-out. For very large audiences most participants are only watching, so you separate the small set of active editors from the large set of viewers. The viewers get their updates through a lighter relay tier rather than the ordering process itself. You also coalesce and rate-limit per socket so one frantic dragger cannot flood the room.

Memory is the harder ceiling. A process holds the whole object map resident, so a hundred-thousand-object file is around a hundred megabytes before you count the recent op tail. A process serves many files, so you cap total resident memory and evict idle files to their snapshot. When someone reopens an evicted file, you spin it back up on demand. A single file too large for one process is the real limit of this model. The honest answer is that you bound file size and lean on snapshotting to keep the resident tail short, rather than pretend one process scales forever.

The Figma angle
Have the crash-recovery story crisp, because the single process is the availability risk and the interviewer knows it. A file is pinned to exactly one process at a time by a consistent-hash mapping or a lease. There is never a moment where two processes both think they own it and diverge the truth. When a process dies, a fresh one loads the latest snapshot for that file, replays the short op-journal tail after it, and re-pins the file to itself. Clients notice the dropped socket, reconnect through the gateway, download the fresh copy, and reapply their buffered edits, the exact offline path from the previous dive reused for a server crash. So a crash costs a reconnect and a re-download, not any acknowledged edit, because durability lives in the replicated journal rather than in the process's memory. The snapshot cadence is a real tuning knob here: snapshot too rarely and recovery replays a long tail, too often and you burn storage on redundant copies.

What is expected at each level

Figma runs one to two system-design rounds inside Figma or FigJam. The design decides level as much as pass or fail, since a design that holds up as the interviewer escalates earns a more senior scope. Figma's ladder is numeric and the internal title names are not published, so the level framing below is approximate. Across every level, remember that collaborative narration is graded: talk through your reasoning and treat the interviewer as a thinking partner.

Mid-level engineer (roughly L2 to L3)

Get the live loop right and defend it. Sessions over WebSockets, one document server per file that orders edits, an op journal as the source of truth, broadcast fan-out to the other clients. Say plainly that the goal is convergence under concurrent edits, and that whole-document last-write-wins is the failure being designed away. Knowing that Figma resolves conflicts with a per-property register rather than a CRDT already puts you ahead. The bar is a coherent end-to-end design you can walk through and stand behind with a nudge or two.

Senior engineer (roughly L3 to L4)

Depth carries it, and design weight rises with level. Reach the register-versus-OT-versus-CRDT tradeoff yourself, pick the server-ordered register, and defend it with its real cost, the single-writer chokepoint and whole-value-per-property resolution. Bring the Figma-specific tree details unprompted: client-minted IDs for offline creation, fractional indexing with parent and position as one atomic property, and cycle rejection on reparenting. Handle offline reconnect as download-fresh-and-reapply, and name what last-writer-wins loses on a contested property. The bar is a near-complete design where you found the hard parts, the conflict model and the single-process recovery, on your own.

Staff engineer (roughly L4 and up)

Lead across the whole system while the interviewer keeps escalating. Reason about a file with hundreds of editors by splitting active editors from viewers behind a relay while keeping one ordering authority. Bound per-process memory and evict idle files to snapshots. Make the process pinning explicit with a lease or consistent hash so a file is never owned twice at once, and tie crash recovery to the same reconnect path clients use offline. Place the boundaries cleanly: where document sync ends and presence, comments, and the rendering engine begin, since forcing all of those through one channel is the common failure. The bar is that you drive the room and the interviewer redirects rather than rescues.

Key Takeaways

  • ✓This is a convergence problem, not a storage problem. Every client must end on an identical object tree whatever order concurrent edits arrived in. Lead with it: whole-document last-write-wins is the failure the whole system exists to prevent, which separates it from a file store that autosaves.
  • ✓Figma uses neither operational transformation nor a pure CRDT. It runs one authoritative server process per file and models the document as a tree of objects (a Map of ObjectID to a Map of Property to Value). It resolves conflicts with a server-ordered last-writer-wins register at the property grain, no timestamp needed. Knowing why Figma rejected both alternatives is the credibility flag; parroting "use a CRDT" is the trap.
  • ✓The tree details are the Figma layer. Client-minted object IDs let creation work offline without collisions. Fractional indexing stores parent and position as one atomic property, so moves are clean. Server-side cycle rejection on reparenting keeps the tree a tree. Reconnect is download-fresh-then-reapply, and last-writer-wins can drop a loser on a contested property, which you name rather than hide.
  • ✓The single process per file makes convergence tractable, and it is also the availability risk. So its recovery story has to be crisp. Pin one owner by lease or consistent hash, recover by loading a snapshot and replaying the journal tail, and reuse the offline reconnect path for a server crash. Presence, comments, and rendering live on their own paths, and knowing where those boundaries sit is part of the answer.
Had your Figma interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Figma interview guide

Know what Figma asks before you walk in.

28 coding · 5 system design · 17 behavioral questions

Get Premium$49 a year · 30-day money-back guarantee
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.