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-09
The Circuit Breaker Pattern, Explained for Resilient Systems
How the circuit breaker pattern stops one slow dependency from taking down your whole service — states, thresholds, and the defaults real libraries ship with.
2026-06-09
What a Merkle Tree Is, and Where You've Already Seen One
A Merkle tree hashes data into a single fingerprint so you can verify any piece without downloading the whole set. Here's how it works and where it already runs in your stack.
2026-06-09
What a Write-Ahead Log Is, and Why Databases Trust It
A practical look at the write-ahead log: the durability trick behind Postgres, SQLite, and most databases, and what it means when a server loses power mid-write.
2026-06-09
Consistent Hashing, Explained Through the Problem It Actually Solves
Why hash(key) % N falls apart when you add a server, how the hash ring fixes it, and what virtual nodes do — a practical walkthrough for developers.
2026-06-08
How DNS Resolution Actually Works, Step by Step
A precise walkthrough of what happens between typing a domain and getting an IP: stub resolver, recursive resolver, root, TLD, and authoritative nameservers, plus TTLs.
Get the best tools, weekly
One email every Friday. No spam, unsubscribe anytime.