← Back
[W] Writing

How multiplayer works in Figma

Two people change the same rectangle at the same moment. One makes it red, the other makes it amber. A second later, both screens agree, and neither person saw it flicker.

Below are two simulated Figma clients and the server between them. Every change travels as a message you can watch. Try it below.

Run "Both recolour the square" with the latency high, and read the steps underneath. A dashed red outline means that client has a change the server hasn't acknowledged yet.

A document is a map of maps

Figma's model is described in Evan Wallace's How Figma's multiplayer technology works, from 2019. Everything here comes from that post and two later ones from Figma's engineering blog.

A Figma file is a tree of objects, a bit like the DOM in a web page. Each object has an ID and a set of properties: its fill, its position, its parent, its text. You can store the whole file as a map from object IDs to maps of properties to values.

Map<ObjectID, Map<Property, Value>>

Object 1:4Frame

fill#ffffff

parent0:1 at 0.5

Object 1:7Rectangle

fill#3b82f6

x, y40, 120

parent1:4 at 0.3

A change is one cell in this table: an object, a property, a new value. The parent link is a property on the child, together with its position among its siblings.

That shape is what makes everything else simple. A change is one cell: object, property, value. Clients send changes at the level of single properties, not whole objects.

The server decides, per property

Every client talks to a server over a WebSocket. In the 2019 design, each open file got its own server process, which held the current copy of the document.

When a change arrives, the server stores the latest value for that property on that object, then sends it to everyone else. Its rule for conflicts fits in one sentence. If two clients change the same property of the same object, the value the server receives last wins.

That's last-writer-wins, but "last" means last to arrive at the server. There are no timestamps and no clock comparison. The server sees changes in one order, so that order is the truth.

Everything else doesn't conflict at all. If you move a rectangle while someone else recolours it, those are two different properties, and both survive. Run "One moves, one recolours" in the demo to see it. Two people only collide when they touch the same cell.

  1. Client applies the change Immediately, on its own screen
  2. Server stores it Latest value per property wins
  3. Ack to the sender The change is no longer pending
  4. Change to everyone else Unless they have their own pending
Clients send changes about every 33ms, according to Figma's 2022 post on reliability, so a drag is a stream of position updates rather than one message at the end.

The part that stops the flicker

Your own changes show up straight away. Waiting a round trip for every drag would feel broken, so the client applies the change locally and tells the server afterwards.

That creates a problem. Say you make the square amber, and a moment earlier someone else made it red. Their red reaches your screen before the server has acknowledged your amber. If your client applied it, your square would flash red, then snap back to amber when the server sent yours round.

So the client keeps a list of its own changes that haven't been acknowledged. When a change arrives from the server for a property on that list, the client ignores it. It already knows its own value will land later and win.

function onServerChange(msg) {
  const key = msg.objectId + "." + msg.property;
  if (pending.has(key)) return; // ours is still in flight, and it'll win
  doc.set(msg.objectId, msg.property, msg.value);
}

function onAck(msg) {
  const key = msg.objectId + "." + msg.property;
  if (pending.get(key) === msg.id) pending.delete(key);
}

This is the rule the demo runs. The ack only clears a property if it's the ack for the most recent change, so a fast drag that sends ten updates stays pending until the tenth comes back.

The server sends messages to each client in order, so the ack for your change arrives before any change the server applied after it. By the time the conflicting value from the other person reaches you, your pending entry is cleared and you apply it. Both screens end up showing the server's answer.

Why not OT, and why not a pure CRDT

Text editors like Google Docs use operational transformation, or OT. Each edit is an operation, like "insert 'a' at position 12", and when two operations cross, each one is rewritten against the other so both sides end at the same text. Wallace describes OT as more complex than Figma needed. It's powerful for long text, where character positions keep shifting, but it has a lot of states to reason about.

A CRDT, a conflict-free replicated data type, is a data structure that merges correctly no matter what order changes arrive in, with no server. Libraries like Yjs use them for text. Figma's design is inspired by CRDTs, but it isn't one. A CRDT pays extra so that any two peers can merge on their own. Figma has a central server, so it can make the server the referee and skip that cost.

There's a trade-off hidden in "per property". In the system the 2019 post describes, a property's value is replaced as a whole. If two people edit the same text layer at once, you get one person's text, not a merge of both. That would be unacceptable in Google Docs. For a design tool, where most edits are to separate objects, it's a fair price.

Ordering layers without renumbering them

The order of layers is the hardest part. If children were stored as an array, moving one layer would change the position of every layer after it, and two people reordering at once would trample each other's numbers.

Figma uses fractional indexing instead, described in Realtime editing of ordered sequences. Each child stores a position: a fraction strictly between 0 and 1. The order of the children is just their positions, sorted. To put a layer between two others, give it the average of their positions.

Drag a layer, or focus one and use the arrow keys. A move changes one index. Then try the two clients inserting at the same spot.

There's always room, because there's always a fraction between two other fractions. The bounds are exclusive, so you can always go before the first layer by averaging with 0, or after the last by averaging with 1.

Doubles would eventually run out of precision, so Figma stores positions as strings with arbitrary precision. It drops the leading "0." and uses base 95, the printable ASCII range, rather than base 10. The demo uses decimal digits so you can read them. One property makes strings a good fit: comparing two of these strings character by character gives the same order as comparing the numbers, so sorting needs no maths.

If two people insert into the same gap at once, they compute the same position. The server spots the clash and gives the second insert a different position. Concurrent inserts can also interleave, which Wallace notes is fine for design files. The objects rarely overlap, and anyone can drag them back.

The parent link and the position are one property. A move to a different parent is a single atomic change, so no one can see a layer with a new parent and an old position. If two people's moves would make a cycle, like A inside B while B goes inside A, the server rejects the update that would create it.

Undo with other people in the room

Undo is harder when you aren't alone. If you undo a colour change after someone else has moved the same object, a naive undo could put back state that overwrites their work.

Figma's principle is that if you undo a lot, copy something, and redo back to the present, the document shouldn't change. Undoing writes to the redo history, and redoing writes to the undo history, using the current values at that moment. Your undo reverses your change, not the world.

Deletion works the same way. When you delete an object, the server drops it entirely, and the data needed to bring it back lives in your undo buffer. The server never has to keep a graveyard of everything anyone ever deleted.

The lesson for anyone building collaborative software is to pick the smallest unit that can conflict. Figma made it one property of one object, and most of the hard problems in collaboration became problems Figma didn't have.

Next