Collaborative Editing with CRDTs

Real-TimeDistributed SystemsIoTArchitecture
Share on LinkedIn

Google Docs made real-time co-editing feel magic. Under the hood, that magic is a convergence algorithm — either Operational Transform or Conflict-free Replicated Data Types — that guarantees two people typing in the same paragraph never permanently diverge. CRDTs have become the default choice for new collaborative products because they merge without a single point of coordination, which means offline editing, peer-to-peer sync, and simpler mental models when the network misbehaves.

I shipped a notes app with Yjs last year. The product requirement was "works in a subway tunnel." CRDTs made that achievable without inventing a custom offline queue and replay protocol.

The convergence problem in one paragraph

Alice and Bob both edit the word "hello" at the same time. Alice inserts "!" at the end; Bob deletes the second "l." What is the document state after both operations arrive? A convergence algorithm must give every replica the same answer, regardless of delivery order. CRDTs achieve this by designing the data structure so merges are associative, commutative, and idempotent — math words that translate to "apply updates in any order, get the same result."

Sequence CRDTs for text

Plain text is an ordered sequence of characters. Sequence CRDTs assign each character (or insert) a unique position identifier that determines sort order when merging:

Deletes are almost always tombstones — mark removed, do not physically erase — because erasing would let a late-arriving insert resurrect in the wrong place. Compaction strategies (snapshots, garbage collection of old tombstones) are essential for long-lived documents.

import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";

const doc = new Y.Doc();
const ytext = doc.getText("content");

// Local edit — instant, no round trip
ytext.insert(0, "Hello");

// Sync over WebSocket
const provider = new WebsocketProvider("wss://sync.example.com", "room-1", doc);

ytext.observe(() => {
  console.log(ytext.toString());
});

Yjs handles the CRDT mechanics; you handle persistence, auth, and UI binding.

Yjs vs Automerge — practical comparison

Both are production-grade. The choice is usually ecosystem fit:

Factor Yjs Automerge
Wire format Binary, compact Binary (Automerge 2.x)
Bindings ProseMirror, TipTap, CodeMirror, Monaco Custom integrations
Persistence y-leveldb, y-indexeddb automerge-repo
Learning curve Lower with editor bindings Lower for JSON-like docs

For rich text with ProseMirror or TipTap, Yjs is the path of least resistance. For JSON-shaped app state (forms, whiteboards, design tools), Automerge's document model maps cleanly.

Architecture for a production CRDT editor

A typical stack:

  1. Client — Yjs doc bound to editor (TipTap + y-prosemirror).
  2. Sync server — WebSocket relay (y-websocket, Hocuspocus, Liveblocks). The server forwards updates; it does not need to understand text.
  3. Persistence — Store Yjs state vectors or update blobs in Postgres/S3. On join, send missing updates since client's state vector.
  4. Auth — Room-level tokens; validate before WebSocket upgrade.

The sync server is not authoritative for conflict resolution — CRDTs merge on the client. The server is a message bus and persistence layer. This is different from OT systems where the server transforms every operation.

Offline flow: edits apply locally, updates queue in IndexedDB (y-indexeddb), replay on reconnect. CRDT merge handles any overlap with remote edits during the offline window.

When OT still wins

CRDTs are not universally superior:

For greenfield collaborative features in 2026, CRDT libraries have matured past the "research prototype" stage. The engineering work shifts from algorithm correctness to persistence, compaction, and access control.

Compaction and document hygiene

Unbounded tombstone growth kills performance. Production systems need:

I snapshot on a timer and on last-client-leave for a room. That keeps cold-start load bounded without losing recent merge capability.

Treat production rollout as a measured change: ship with observability, validate rollback, and review metrics 24 hours after deploy — patterns that look obvious in docs fail when skipped under release pressure.

Common production mistakes

Teams get collaborative editing crdt wrong in predictable ways:

Production implementations of collaborative editing crdt fail when staging mirrors production topology poorly, rollback is untested, and on-call runbooks describe the happy path only.

Debugging and triage workflow

When collaborative editing crdt misbehaves in production, work top-down instead of guessing:

  1. Confirm scope — one tenant, region, or deployment stage? Narrow blast radius before deep diving.
  2. Check recent changes — deploys, flag flips, config pushes, and schema migrations in the last 24 hours.
  3. Compare golden signals — latency, error rate, saturation, and traffic for the affected surface vs. baseline.
  4. Reproduce minimally — smallest input or scenario that triggers the failure; capture traces/logs with correlation IDs.
  5. Fix forward or rollback — if rollback is faster than root-cause during incident, rollback first, postmortem second.
  6. Add a guard — alert, integration test, or circuit breaker so the same class of failure is caught earlier next time.

Document the timeline during triage. Future you (and on-call) will need timestamps, not just conclusions.

Resources

Frequently asked questions

What is a CRDT and why use one for collaborative editing?

A Conflict-free Replicated Data Type is a data structure designed so concurrent updates from multiple users always merge to a consistent state without coordination. For collaborative editing, sequence CRDTs (like Yjs's Y.Text or Automerge's Text) let each client apply local edits instantly and merge remote edits deterministically — no central server deciding winner/loser on every keystroke.

How do CRDTs differ from Operational Transform?

OT transforms operations against each other relative to a shared document revision, usually requiring a central server or strict ordering. CRDTs embed causality in the data structure itself, so peers can merge offline and out-of-order. OT tends to be more compact for plain text; CRDTs tolerate partition and decentralization better.

What are the main downsides of CRDT-based editors?

CRDTs carry metadata overhead — tombstones, unique IDs, or fractional indices — that grows with edit history unless compacted. Large documents with years of edits can become heavy. Debugging merge behavior is harder than a single authoritative server log. For many products, a hosted service (Liveblocks, PartyKit with Yjs) abstracts this cost.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →