| Myth | Reality |
|---|---|
| CRDTs and Operational Transform solve the same problem, so the choice is just a matter of preference. | Both converge concurrent edits to the same result, but OT requires a central server to transform and order every operation, while CRDTs are designed to merge correctly even peer-to-peer with no server arbitrating — a structural difference, not a stylistic one. |
| Once you’ve picked a CRDT library, “real-time collaboration” is basically solved. | The CRDT only handles conflict-free data merging. You still need a presence/awareness layer for cursors and selections, a sync transport (WebSocket or WebRTC), persistence and snapshotting so documents don’t grow unbounded, and access control — none of which the CRDT library provides for you. |
| Any article that mentions CRDTs is covering the same ground as this one. | CRDTs show up narrowly in offline-first mobile sync (merging a counter or list after a phone reconnects) and in local-vs-cloud storage trade-off discussions. Building live, simultaneous multi-user editing — the Google Docs/Figma/Linear problem — is a distinct engineering domain with its own libraries, transports, and failure modes. |
One clarification before anything else: this article is about real-time, simultaneous multi-user editing — multiple people typing into the same document or canvas at the same moment and seeing each other’s cursors live, the way Google Docs, Figma, and Linear work. That is a different engineering problem from offline-device sync. Our Local vs Cloud Data Storage in Mobile Apps piece touches CRDTs briefly inside a mobile storage trade-off discussion, and 12 Steps to Building Offline-First Mobile Apps uses CRDTs narrowly for merging counters, sets, and lists when a phone reconnects after being offline, inside an outbox-pattern architecture. Neither is about the topic here: the algorithms, libraries, presence protocols, and transport choices behind live, concurrent, multi-cursor collaborative editing.
Two Families of Solutions: CRDTs and Operational Transform
Both CRDTs (Conflict-free Replicated Data Types) and Operational Transform (OT) exist to answer the same question — when two people edit the same document at the same instant, how do you guarantee every replica converges to an identical result without anyone’s edit silently disappearing? OT was pioneered by Ellis and Gibbs in the 1989 GROVE system and became famous as the algorithm behind Google Wave and, later, Google Docs: every operation carries metadata about the document state it was created against, and when it arrives at a central server, the server transforms it against any operations applied since, adjusting positions so it applies correctly to the current state. OT works well but structurally depends on a server to be the single source of truth for ordering and transforming operations — clients cannot safely merge directly with each other.
CRDTs took a different approach: design the data structure itself so that any two replicas, given the same set of operations in any order, mathematically converge to the same state — no central arbiter required. That property makes CRDTs a natural fit for peer-to-peer topologies (WebRTC mesh, no server in the data path) as well as conventional client-server setups, and it’s why nearly every collaborative-editing library built since the mid-2010s (Yjs, Automerge, Loro) is CRDT-based rather than OT-based. OT hasn’t disappeared — Google Docs itself, and newer approaches like the Eg-walker algorithm Figma uses for live code collaboration, still rely on transform-based techniques — but for teams building new collaborative products today, CRDT libraries are the default starting point.
| Dimension | Operational Transform | CRDTs |
|---|---|---|
| Requires a central server? | Yes — the server transforms and orders every operation | No — replicas can merge directly, peer-to-peer or client-server |
| Implementation complexity | Notoriously hard to get right; transform functions must handle every operation-pair combination correctly | Complex internally, but usually consumed via a battle-tested library rather than hand-rolled |
| Best-known production example | Google Docs | Figma (canvas), Linear, most editors built on Yjs/Automerge since ~2018 |
| Offline support | Weak — designed around a live connection to the transform server | Strong — operations can be generated offline and merged whenever connectivity returns |
Choosing a CRDT Library: Yjs vs Automerge vs Loro
Yjs remains the production default for collaborative editing in 2026-2027, with roughly 920K weekly npm downloads, the largest ecosystem of editor bindings (Tiptap, ProseMirror, Monaco, Quill), and a network of hosted/self-hosted sync providers. Its core design prioritizes speed for text and structured data over storing full edit history. Automerge takes a different trade-off: it’s a Rust-core, WASM-bound library with a genuinely JSON-like data model and Git-like change history built in, so document branching, merging, and “who changed this and when” attribution are first-class features rather than something you bolt on. Loro is the newest of the three, also Rust-based, and benchmarks consistently show it leading on raw performance and encoded-document size for large documents and many concurrent editors, though its ecosystem of framework bindings is still younger than Yjs’s.
- Pick Yjswhen you want the most mature ecosystem, the widest range of existing editor bindings, and you don’t need built-in version history as a product feature.
- Pick Automergewhen document branching, time-travel, and change attribution are things your product actually needs to expose to users, not just an internal implementation detail.
- Pick Lorowhen you’re building for very large documents or high concurrent-editor counts and raw merge performance and payload size are the binding constraint.
Presence and Awareness: The Layer CRDTs Don’t Handle
A CRDT library merges document content — it says nothing about whose cursor is where, who’s currently viewing the document, or what someone has selected. That’s handled by a separate, deliberately ephemeral layer usually called an “awareness” protocol. Yjs’s own awareness implementation is itself a small, non-persisted CRDT: each connected client broadcasts a JSON blob (cursor position, selection range, username, color) to every other client, and because this state doesn’t need to survive a reload, it’s never written to the document itself or persisted to disk. This separation matters architecturally — document content needs durability and conflict resolution, while presence needs low latency and disposability, and conflating the two tends to bloat your persisted document with throwaway cursor-position noise.
Figure 1 — Two data planes in a collaborative editor
A diagram shows a central document icon labeled “CRDT document state (Yjs/Automerge/Loro)” with a lock icon indicating it’s persisted to a database or object store. Beside it, a separate lighter-weight icon labeled “Awareness state (cursors, selections, presence)” has a dashed border indicating it is ephemeral and held only in memory / broadcast over the live connection, never written to the persisted document. Both planes connect to a central “Sync server / relay” box, which fans out to three client icons, each showing a colored cursor and a small avatar.
Keeping presence data out of the persisted CRDT document is what keeps document size bounded — cursors move constantly, but nobody needs that history saved.
Conflict-Free Rich-Text Merging
Plain-text CRDTs are relatively well understood — each character gets a stable identifier, and insertions/deletions merge deterministically based on those identifiers rather than raw array positions. Rich text is harder because formatting (bold, italic, headings, embedded images) has to survive concurrent edits too: if one person bolds a word while another person deletes the sentence around it, the merge has to produce a sensible result, not a garbled mix of formatting marks and orphaned text. Yjs handles this with a specialized type (Y.XmlFragment / Y.Text with formatting attributes) that treats formatting spans as their own mergeable structure layered on top of the character sequence, while Automerge and Loro take a similar approach of modeling rich text as a sequence CRDT with attached, independently-mergeable attribute ranges. The practical upshot for engineering teams: don’t try to hand-roll rich-text CRDT merge logic — lean on the editor bindings (Tiptap’s Yjs extension, ProseMirror’s y-prosemirror) that have already solved the formatting-merge edge cases most teams would otherwise discover in production.
Sync Transport: WebSocket vs WebRTC
Getting CRDT updates from one client to another needs a transport, and the two dominant choices carry different trade-offs. A WebSocket connection to a central relay server is the simpler, more common architecture: the server doesn’t need to understand the CRDT’s internal structure — it just rebroadcasts opaque update messages to every other connected client — which keeps the server thin and makes horizontal scaling, access control, and persistence straightforward to reason about. WebRTC data channels enable direct peer-to-peer sync with no server in the data path at all, which minimizes latency and server cost for small groups, but requires a signaling server for initial connection setup, deals with NAT traversal (STUN/TURN) reliability issues, and gets structurally harder to reason about once you’re syncing more than a handful of peers directly, since every peer needs a connection to every other peer.
Most production collaborative editors — Figma, Linear, and the majority of Yjs-based products — use a WebSocket relay to a lightweight sync server (y-websocket, or Figma’s own multiplayer service model) rather than pure peer-to-peer WebRTC, precisely because the server also becomes the natural place to persist document snapshots, enforce access control, and avoid the N-squared connection problem of pure mesh topologies. WebRTC tends to show up in explicitly peer-to-peer or local-network collaborative tools where a server is undesirable or unavailable, not as the default choice for a hosted SaaS product.
Architecture Patterns for Building a Collaborative App
A production-grade collaborative editor generally needs five pieces working together: the CRDT document model itself; a thin sync server that relays updates and persists periodic snapshots (rather than replaying the entire operation history on every load); an awareness channel for presence; a snapshot/compaction strategy since CRDT metadata grows with every edit and needs periodic garbage collection (Yjs’s garbage-collection option, or Automerge’s compaction); and an access-control layer that gates who can join a document’s sync session at all, since the CRDT layer itself has no concept of permissions. Figma’s own engineering writeups describe a write-ahead log for their multiplayer service specifically to make reconnection after a dropped connection reliable — a detail that matters more in production than most getting-started CRDT tutorials mention, since real users have real network interruptions mid-edit.
Common mistakes
- Storing awareness/presence state (cursor positions) inside the persisted CRDT document instead of a separate ephemeral channel, bloating document size for no benefit.
- Hand-rolling rich-text formatting merge logic instead of using an existing editor binding (Tiptap, ProseMirror) that has already solved the concurrent-formatting edge cases.
- Choosing pure WebRTC mesh sync for a product that will have more than a handful of simultaneous editors, hitting the N-squared connection scaling wall.
- Never compacting or snapshotting CRDT history, letting document metadata grow unbounded until load times degrade.
- Treating this as the same problem as offline-mobile-sync CRDT usage and importing patterns (like an outbox queue) that don’t fit a live, always-connected editing session.
In practice
Teams shipping real collaborative editors typically start from an existing editor binding rather than the raw CRDT library — Tiptap or ProseMirror with y-prosemirror for rich text, or a canvas-specific sync layer for design tools — because the formatting-merge and cursor-mapping edge cases are already handled. They treat the sync server as a thin relay plus periodic snapshot writer, not a place to embed business logic, mirroring the same “keep shared infrastructure simple and centrally owned” discipline covered in our guide to Git and version control in collaborative projects — both are, at their core, systems for merging concurrent changes without losing anyone’s work. Before committing to a library, most teams also validate the underlying mobile and web SDK ecosystem they’ll be pulling editor bindings from, the same evaluation process outlined in our guide to popular mobile app libraries and SDKs, and scope a thin vertical slice — one document type, one editor binding, basic presence — the same MVP-first approach described in our guide to building an MVP for a mobile app idea, before expanding to canvas or multi-document collaboration.
Glossary
- CRDT
- Conflict-free Replicated Data Type — a data structure designed so that replicas converge to the same state regardless of the order operations are applied, without a central arbiter.
- Operational Transform (OT)
- A collaborative-editing algorithm that transforms each operation against concurrent ones at a central server so all clients converge to the same document state.
- Awareness protocol
- An ephemeral, non-persisted channel (separate from document content) that broadcasts presence information like cursor position, selection, and online status.
- Eg-walker
- A transform-based collaborative editing algorithm used by Figma for live multiplayer code-layer collaboration.
- WebRTC data channel
- A peer-to-peer transport for arbitrary data exchange between browsers, used in some collaborative tools to sync without a relay server in the data path.
- Snapshot / compaction
- Periodically collapsing a CRDT’s accumulated operation history into a compact current-state representation to keep document size and load time bounded.
- Rich-text CRDT
- A CRDT model that merges both character content and formatting attributes (bold, headings, links) concurrently without corrupting either.
- N-squared connection problem
- The scaling limitation of pure peer-to-peer mesh topologies, where each additional peer requires a direct connection to every existing peer.
Key Takeaways
- This article covers live, simultaneous multi-user editing — not offline-device sync, which is a related but distinct use of CRDTs covered elsewhere on this site.
- OT (Google Docs’ original algorithm) requires a central server to transform operations; CRDTs are designed to merge correctly with no central arbiter, including peer-to-peer.
- Yjs is the mature default; Automerge trades some performance for built-in version history; Loro leads on raw performance for large documents and many editors.
- Presence/awareness (cursors, selections) is a separate, deliberately ephemeral layer from the persisted CRDT document — don’t conflate the two.
- Rich-text merging should lean on existing editor bindings (Tiptap, ProseMirror) rather than hand-rolled formatting-merge logic.
- Most production collaborative editors use a thin WebSocket relay server, not pure WebRTC mesh, once you’re past a handful of simultaneous editors.
- A full architecture needs the CRDT model, a sync/relay server, an awareness channel, snapshot/compaction, and access control — the CRDT library alone solves only one of the five.
FAQs
What is the core difference between CRDTs and Operational Transform?
OT requires a central server to transform and order every incoming operation against concurrent ones. CRDTs are data structures engineered so replicas converge to the same state regardless of operation order, without requiring a central arbiter — which is what makes them viable for peer-to-peer sync as well as client-server setups.
Is this the same topic as CRDTs used in offline-first mobile apps?
No. Offline-first mobile sync uses CRDTs narrowly to merge specific data types (counters, sets, lists) after a device reconnects, inside an outbox-pattern architecture. This article covers live, simultaneous multi-user editing — multiple people typing into the same document at once with visible cursors — a distinct engineering domain with its own libraries and transport choices.
Should I choose Yjs, Automerge, or Loro for a new collaborative editor?
Yjs is the safest default given its ecosystem maturity and editor bindings. Choose Automerge if built-in version history and change attribution are product requirements, not just implementation details. Choose Loro if you’re optimizing specifically for large documents or high concurrent-editor counts and raw performance is the binding constraint.
Do I need WebRTC for real-time collaboration, or is WebSocket enough?
WebSocket to a thin relay server is the standard choice for most production collaborative editors, including Figma and most Yjs-based products, because it simplifies persistence, access control, and scaling past a handful of users. WebRTC peer-to-peer sync is better suited to explicitly serverless or local-network tools with small numbers of participants.
How is cursor and presence data handled separately from document content?
Through a separate “awareness” protocol — Yjs’s implementation is itself a small, non-persisted CRDT that broadcasts JSON state (cursor position, selection, username, color) to connected clients without ever writing it into the document itself, keeping ephemeral presence data from bloating persisted document size.
How do collaborative editors merge concurrent formatting changes in rich text?
Rich-text CRDTs model formatting attributes (bold, headings, links) as their own mergeable structure layered on top of the underlying character sequence, so a bold applied by one user and a delete applied by another converge to a sensible result. Most teams rely on existing editor bindings like Tiptap or ProseMirror’s y-prosemirror rather than implementing this merge logic themselves.
Does Google Docs use CRDTs or Operational Transform?
Google Docs’ collaborative editing traces back to Operational Transform, adopted from the Google Wave project in 2009. Newer collaborative tools built since roughly 2018, including most Yjs- and Automerge-based products, have generally favored CRDT-based approaches instead.
What keeps a CRDT document’s size from growing unbounded over time?
Periodic snapshotting or compaction — collapsing the accumulated operation history into a compact current-state representation. Yjs supports garbage-collecting old state, and Automerge/Loro support similar compaction, both of which are necessary in production since every edit otherwise adds permanently to the document’s stored metadata.
What’s the minimum architecture needed beyond just picking a CRDT library?
Five pieces: the CRDT document model, a sync/relay server (typically WebSocket-based), a separate awareness/presence channel, a snapshot or compaction strategy to bound document growth, and an access-control layer, since the CRDT library itself has no concept of permissions or who’s allowed to join a session.
References
- “Operational transformation,” Wikipedia, https://en.wikipedia.org/wiki/Operational_transformation
- “Awareness and Presence,” Yjs Docs, https://docs.yjs.dev/getting-started/adding-awareness
- “How Figma’s multiplayer technology works,” Figma Blog, https://www.figma.com/blog/how-figmas-multiplayer-technology-works/
- “Making multiplayer more reliable,” Figma Blog, https://www.figma.com/blog/making-multiplayer-more-reliable/
- “Canvas, Meet Code: Building Figma’s Code Layers,” Figma Blog, https://www.figma.com/blog/building-figmas-code-layers/
- “Loro Performance Benchmarks,” Loro, https://loro.dev/docs/performance
- “Automerge Documentation,” Automerge, https://automerge.org/