What a CRDT Is, and How Collaborative Apps Stay in Sync
A practical explainer on conflict-free replicated data types: the merge math behind them, the main CRDT families, and how libraries like Yjs and Automerge use them.
Open the same document in two browser tabs, go offline in both, type a different sentence in each, then reconnect. A good collaborative editor will show you both sentences, in a stable order, on every device — no “resolve conflict” dialog, no lost keystrokes. The data structure that makes that possible is usually a CRDT.
CRDT stands for Conflict-free Replicated Data Type. The name is a mouthful, but the idea it encodes is narrow and precise: a data type whose replicas can be edited independently and then merged automatically, with a mathematical guarantee that every replica ends up identical once they have all seen the same set of changes. No central referee decides who wins. The merge rule itself does the deciding, and it does so the same way no matter what order the changes arrive in.
The merge math that makes “conflict-free” true
The word “conflict-free” is doing real work. It does not mean two people can never touch the same character. It means the merge function is designed so there is always a defined, deterministic answer, and that answer does not depend on timing or network order.
Concretely, a state-based CRDT (the literature calls these CvRDTs, for convergent) defines a merge operation over its values, and that operation has to satisfy three properties:
- Commutative:
merge(a, b) == merge(b, a). Order of arrival does not matter. - Associative:
merge(merge(a, b), c) == merge(a, merge(b, c)). Grouping does not matter. - Idempotent:
merge(a, a) == a. Receiving the same update twice changes nothing.
If merge has all three, the set of possible states forms a structure called a join-semilattice, and replicas are guaranteed to converge. That guarantee is the whole point. You can drop packets, deliver them out of order, deliver them twice, or sync three devices in a triangle — and as long as every change eventually reaches every replica, they all land on the same value.
There is a second flavor, operation-based CRDTs (CmRDTs, for commutative). Instead of shipping whole states and merging them, replicas broadcast individual operations, and the operations are designed to commute. This is lighter on bandwidth but assumes the network delivers every operation exactly once. Most production libraries blur the line and use an optimized hybrid, but the convergence requirement is the same.
The CRDT zoo: counters, sets, registers, and text
CRDTs come as a toolkit of building blocks, each solving one shape of data.
Counters. A G-Counter (grow-only) gives each replica its own slot and only ever increments its own. The counter’s value is the sum of all slots; merge takes the max of each slot. A PN-Counter pairs two G-Counters, one for increments and one for decrements, so you can support subtraction without breaking convergence.
Registers. A single value that gets overwritten. The common version is a Last-Write-Wins register, which attaches a timestamp (usually a logical clock, not a wall clock) to each write and keeps the highest. LWW is simple and is exactly where the “convergence is not intent” warning bites: concurrent writes mean one silently loses.
Sets. A G-Set only allows adds. An OR-Set (observed-remove) lets you add and remove the same element repeatedly by tagging each add with a unique id, so a remove only cancels the specific adds it has seen. This is how you avoid the classic bug where one replica’s “remove” accidentally erases another replica’s later “add.”
Sequences. This is the hard one, and it is what powers collaborative text. A sequence CRDT assigns each character a position identifier that sorts stably between its neighbors, so two people inserting at “the same spot” get distinct, orderable positions instead of clobbering each other. Algorithms here have names like RGA, Logoot, and YATA. YATA is the model behind Yjs; Automerge uses a related approach.
Notion
A widely used collaborative workspace where multiple editors can work on the same page in real time — the kind of multiplayer editing experience CRDT-style sync is built to support.
Free tier; paid plans from $10/user/mo
Affiliate link · We earn a commission at no cost to you.
How a real collaborative app uses one
Put the pieces together and a multiplayer editor looks like this. Each client holds a full replica of the document as a CRDT. When you type, the client applies the change locally and immediately — that is why good collaborative apps feel instant even on a bad connection. The change is also encoded as a compact update and sent to other clients, usually relayed through a lightweight server or a peer mesh.
The server in this model often does almost no merging logic of its own. With a library like Yjs, the server can be a dumb relay plus a persistence layer: it forwards binary updates and stores them so a client joining late can catch up. The convergence guarantee lives in the CRDT, not the server, which is what lets these systems keep working offline and resync cleanly later.
The main alternative you will hear about is Operational Transformation (OT), the older approach behind Google Docs. OT transforms each operation against concurrent ones to preserve intent, but it traditionally needs a central server to order operations, which makes true peer-to-peer and offline-first harder. CRDTs trade some metadata overhead for not needing that central authority. Neither is strictly better; they sit at different points on the same tradeoff curve.
FAQ
Do I need to implement a CRDT myself?
Are CRDTs only for text editors?
What happens with a true conflict, like two people editing the same value?
The one-sentence version to keep: a CRDT is a data type whose merge rule is commutative, associative, and idempotent, which mathematically forces every replica to converge no matter how the network behaves. Everything else — the counters, the sets, the sequence algorithms — is engineering around that single guarantee.
Related reading
2026-06-22
TCP vs UDP, Explained Through What Breaks When You Pick Wrong
TCP and UDP aren't interchangeable. We walk through the exact failure modes — head-of-line blocking, silent packet loss, Nagle delays — that show up when you pick the wrong transport.
2026-06-22
Write-Ahead Logging: How Databases Survive a Power Cut
How write-ahead logging keeps your data intact when the machine dies mid-write — the log-first rule, fsync, checkpoints, and why PostgreSQL and SQLite both rely on it.
2026-06-22
Backpressure, Explained Through a Queue That Won't Fall Over
What backpressure actually is, why an unbounded queue is a memory leak in disguise, and the four strategies a producer can take when a consumer falls behind.
2026-06-22
What a Bloom Filter Actually Saves You (and When It Lies)
A bloom filter trades a small false-positive rate for big memory savings. Here is the math behind the trade, where it pays off, and the failure mode that bites people.
2026-06-22
Idempotency, Explained Through the Retry That Doesn't Double-Charge
A practical look at idempotency keys: why a retried payment request shouldn't charge a card twice, how the pattern works, and where it quietly breaks in production.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.