Two field engineers edit the same inspection report, one of them on a site with no signal. Both save. Whatever happens next is your consistency model, and in most applications it is the same model: last write wins, decided by whichever device reconnected second, silently discarding an hour of someone's work.
CRDTs replace that coin flip with a data structure whose merge function is mathematically guaranteed to converge. Any two replicas that have seen the same set of updates end up in the same state, regardless of order, duplication or timing. No locks, no central arbiter, no coordination round trip before the user can type.
Why the guarantee holds
The merge operation is commutative, associative and idempotent. Order does not matter, grouping does not matter, and applying the same update twice changes nothing — which is precisely the set of properties an unreliable network violates in every other design. That is why CRDT sync protocols can be so casual about delivery: at-least-once and out-of-order is a fully supported mode of operation, not an edge case.
For text and lists, modern sequence CRDTs assign each character a stable identifier in a dense order, so concurrent inserts interleave deterministically rather than fighting over indices. The libraries worth using — Yjs and Automerge being the mature ones — have solved the memory representation and the wire format, which is the part that used to make this academic rather than practical.
// Each client holds the whole document; the server is a relay, not a referee.
const doc = new Y.Doc();
const findings = doc.getArray('findings');
// Offline edits are just edits. No queue, no conflict dialog.
findings.push([{ id: crypto.randomUUID(), note: 'Cracked seal, unit 4B' }]);
// Sync is an exchange of updates. Order and duplication do not matter.
provider.on('update', (update) => Y.applyUpdate(doc, update));
doc.on('update', (update) => provider.broadcast(update));
// Server-side persistence is the same primitive: merge, then store.
const merged = Y.mergeUpdates([storedUpdate, incomingUpdate]);Convergence is not correctness
This is the part that gets skipped. CRDTs guarantee that everyone ends up with the same document. They do not guarantee that the document means what anyone intended. Two people concurrently approving and rejecting the same report converge on a consistent state, and that state may be nonsense from the business's point of view.
So model what is actually concurrent. Free-form collaborative content — notes, descriptions, checklists — is an excellent fit. Anything with an invariant across fields, a monotonic counter that must not go backwards, or a decision that only one party may make, still needs server-side authority. The good designs I have seen are hybrids: CRDT for the document body, conventional authorization and validation for state transitions.
- Storage grows with edit history, not document size. Plan for periodic compaction and snapshotting from the start.
- Permissions are not a CRDT property. A client holding the full document can read all of it — enforce access by partitioning documents, not by hiding fields.
- Deletion is a tombstone. Real erasure, for privacy or compliance, needs an out-of-band rewrite of history.
- Schema migration is genuinely hard when old clients hold old structures offline for weeks. Version explicitly and tolerate both.
- Test convergence with randomised concurrent operations, not scripted scenarios; the bugs live in orders you would not think to write.
A CRDT guarantees that everyone agrees on the answer. Whether the answer is right is still your job.
The reason to care beyond offline support is latency. Local-first applications apply edits to local state immediately and sync in the background, so every interaction is instant and the network becomes an implementation detail rather than a prerequisite. For field tooling — the inspection app, the warehouse scanner, the site survey — that is not a feature, it is the difference between software people use and software people work around.