How Does Figma Let 50 People Edit at Once?

How Does Figma Let 50 People Edit at Once?

The Problem: Concurrent Edits Without Locks

Two users editing the same Google Doc. Two players in the same game world. Two data centers accepting writes to the same key. How do you merge concurrent changes without conflicts?

Traditional approach: use locks or consensus (Paxos, Raft). One writer at a time, or agree on an order. But locks kill performance, and consensus requires coordination across data centers — adding latency. What if the data structure itself could merge automatically, with no coordination needed?

That is what CRDTs (Conflict-free Replicated Data Types) do. They are data structures designed so that concurrent updates always merge deterministically. No matter what order updates arrive, all replicas converge to the same state. This property is called strong eventual consistency.

Figma uses CRDTs to enable real-time collaborative design with 50+ simultaneous editors. Every operation is applied locally with zero latency, then synced to other clients. Conflicts resolve automatically.

How Do CRDTs Work?

CRDTs come in two flavors:

  • State-based (CvRDTs): replicas exchange their full state. States are merged using a join function that is commutative, associative, and idempotent. Think: set union, max of two numbers.
  • Operation-based (CmRDTs): replicas exchange operations (not state). Operations must be commutative — applying them in any order produces the same result. Think: increment a counter.
G-Counter CRDT: Convergence Without Coordination Replica A { A: 3, B: 0 } → total: 3 A incremented 3x locally Replica B { A: 0, B: 5 } → total: 5 B incremented 5x locally merge = max per key Merged: { A: 3, B: 5 } total: 8 (correct!)

Figure 1: A G-Counter CRDT. Each replica only increments its own slot. Merge takes the max of each slot. The total is always correct regardless of merge order.

Common CRDT Types

  • G-Counter: grow-only counter. Each node has its own counter. Value = sum of all counters. Merge = max per node.
  • PN-Counter: supports increment and decrement. Two G-Counters — one for positive, one for negative. Value = P - N.
  • G-Set: grow-only set. Elements can only be added, never removed. Merge = union.
  • OR-Set (Observed-Remove Set): supports add and remove. Each element has a unique tag. Remove only removes observed tags. Add wins over concurrent remove.
  • LWW-Register: last-writer-wins register. Each write has a timestamp. Merge picks the highest timestamp. Simple but loses concurrent writes.
  • Sequence CRDT: for collaborative text editing. Characters have fractional positions between neighbors. Used by Yjs and Automerge.

CRDTs in the Wild

  • Redis: Redis CRDB (conflict-free replicated database) uses CRDTs for active-active geo-replication. Counters, sets, and strings all have CRDT semantics.
  • Riak: built-in CRDT support for counters, sets, maps, and flags. Every Riak data type is a CRDT.
  • Apple: Notes app uses CRDTs for sync across iCloud devices.
  • Figma: custom CRDTs for real-time collaborative design.
  • Yjs: open-source CRDT framework powering collaborative editors. Used by JupyterLab, Tiptap, and BlockSuite.

Yjs can handle 100,000+ concurrent edits per second with sub-millisecond merge times. The document state is typically just 1.5-2x the size of the raw text, making it practical for real-world collaborative editing.

The Tradeoff: Metadata Overhead

CRDTs are not free. They carry metadata — version vectors, tombstones for deletions, unique tags for set elements. A CRDT-based text document might use 2-10x more memory than the raw text. Tombstones (markers for deleted elements) accumulate over time and need garbage collection. Designing an efficient CRDT requires careful engineering to minimize this overhead.

References and Further Reading