Skip to main content
KBY Technologies Logo
root/software-architecture/architecting-crdt-sync-for-offline-first-apps

Architecting CRDT Sync for Offline-First Apps

How lattice-based merge functions, delta-state sync, and causal metadata replace last-write-wins in offline-first CRDT conflict resolution architectures.
Architecting CRDT Sync for Offline-First Apps
15/07/2026|11 min read|

Offline-first architectures fail in a predictable place: the merge step. Two clients edit the same document while partitioned from the network, reconnect, and the naive resolution strategy — last-write-wins on a server timestamp — silently discards one user’s work. At scale, this isn’t an edge case; it’s the dominant failure mode for any distributed system that accepts writes at the edge before establishing consensus. CRDT conflict resolution exists specifically to remove the coordination requirement from that merge step, replacing arbitration with mathematics.

This article covers the architectural decisions behind building a production-grade sync engine using Conflict-Free Replicated Data Types, including primitive selection, causal metadata design, tombstone

garbage collection, and the operational trade-offs that show up once you have tens of thousands of concurrently editing replicas rather than a two-node demo.

#The Divergence Problem in Offline-First Architectures

Consider a mobile field-service application: technicians edit job records on-site with no connectivity, then sync when they re-enter range. Two technicians independently update the same job’s status field. A central server, when it finally receives both writes, has three options: reject one, merge both, or ask a human. Rejecting a write loses data. Asking a human doesn’t scale past a few conflicts a day. Merging requires a data structure that guarantees the same result regardless of the order operations arrive in — this is the exact property CRDTs

provide.

The formal requirement is strong eventual consistency (SEC): given the same set of updates, delivered in any order, any number of times, all replicas converge to an identical state. This is stronger than eventual consistency alone, which only guarantees convergence, not determinism of the merge outcome.

#Why Last-Write-Wins Fails at the Architectural Level

LWW based on wall-clock timestamps introduces two structural problems. First, clock skew across devices — even with NTP, drift of 200-500ms is common on consumer hardware, and offline devices can drift for days. Second, and more fundamentally, LWW collapses a rich edit history into a single winner, which is semantically wrong for anything beyond scalar fields. Concatenated text, ordered lists, and counters all need field-level or operation-level resolution, not document-level arbitration.

Vector clocks

solve the ordering problem without relying on wall-clock time, but ordering alone doesn’t tell you how to merge two concurrent (causally unrelated) operations — it just tells you they’re concurrent. That’s the gap CRDT conflict resolution fills: each data type defines a deterministic merge function over its own algebraic structure.

CRDT conflict resolution

#CRDT Conflict Resolution: Lattice Theory Instead of Timestamps

Every CRDT

is built on a join semi-lattice — a set of states with a merge operation that is commutative, associative, and idempotent. Concretely: merge(a, b) == merge(b, a), merge(merge(a,b),c) == merge(a,merge(b,c)), and merge(a,a) == a. If your merge function satisfies those three properties, you get convergence for free — no coordinator, no consensus round, no quorum write. This is the theoretical basis underpinning most modern architectural patterns for edge-first and multi-region systems.

#State-Based vs Operation-Based CRDT Conflict Resolution

There are two families:

  • CvRDTs (convergent/state-based): replicas exchange full state; merge is a pure function of two states. Simple to reason about, but bandwidth cost grows with document size unless you implement delta-state variants.
  • CmRDTs (commutative/operation-based): replicas exchange individual operations, which must be delivered with causal ordering (not total ordering) via a reliable causal broadcast layer. Bandwidth-efficient, but the transport layer carries more responsibility.

Most production systems — Automerge, Yjs, Riak’s riak_dt — use delta-state CRDTs, a hybrid that ships only the state delta since the last sync, giving CmRDT-like bandwidth with CvRDT-like merge simplicity.

#Implementation Logic: Building a Sync Engine

#Step 1 — Choosing the Right CRDT Primitive

Primitive selection is the single highest-leverage decision in the whole design. Picking a G-Set where you need an OR-Set means you can never delete an item. Picking an LWW-Register for free-text fields means concurrent edits silently overwrite each other instead of interleaving.

CRDT TypeUse CaseMetadata OverheadConvergence Guarantee
G-CounterMonotonic counters (view counts)O(n) replicasExact sum, no decrement
PN-CounterIncrement/decrement counters (stock levels)2× G-CounterExact sum, supports decrement
LWW-RegisterScalar fields (status, priority)Timestamp + replica ID per fieldDeterministic, lossy on true concurrency
OR-SetTag lists, collaborative membershipUnique tag per add operation (tombstones on remove)Add-wins semantics
RGAOrdered sequences, collaborative textTombstoned linked list, grows with edit historyDeterministic interleaving of concurrent inserts

#Step 2 — Causal Metadata and Vector Clocks

Every op-based CRDT needs enough metadata to detect concurrency. A vector clock per replica, or a dot-based scheme (unique (replicaId, counter) pairs per operation, as used in delta-CRDTs) is the standard approach. This is where most naive implementations bloat: storing a full vector clock

per field, per document, per replica scales badly once you pass a few hundred concurrent editors.

1interface Dot {
2  replicaId: string;
3  counter: number;
4}
5
6interface LWWRegister {
7  value: T;
8  dot: Dot;
9  timestamp: number; // tie-breaker only, never primary ordering
10}
11
12function mergeLWW(a: LWWRegister, b: LWWRegister): LWWRegister {
13  if (a.timestamp !== b.timestamp) {
14    return a.timestamp > b.timestamp ? a : b;
15  }
16  // Deterministic tie-break on replicaId prevents split-brain divergence
17  return a.dot.replicaId > b.dot.replicaId ? a : b;
18}

Note the tie-breaker: relying on timestamp alone for CRDT conflict resolution reintroduces clock-skew risk. The replicaId comparison guarantees a total order even when two writes land in the same millisecond, which happens frequently under batched sync.

CRDT conflict resolution

#Step 3 — Merge Protocol and Transport

For an OR-Set, adds carry a unique dot and removes carry a tombstone

referencing the exact dot being removed, not the value itself. This is what gives OR-Sets add-wins semantics under concurrent add/remove pairs.

1{
2  "docId": "job-88213",
3  "field": "tags",
4  "type": "or-set-delta",
5  "adds": [
6    { "value": "urgent", "dot": { "replicaId": "r-mobile-04", "counter": 17 } }
7  ],
8  "removes": [
9    { "dot": { "replicaId": "r-mobile-02", "counter": 9 } }
10  ],
11  "causalContext": {
12    "r-mobile-04": 17,
13    "r-mobile-02": 9,
14    "r-server-00": 44
15  }
16}

The causalContext field is what allows a receiving replica to detect whether it has already seen this delta (idempotency) and whether it is missing any prior deltas (causal gap detection) before applying it — critical for correct CRDT conflict resolution under lossy or reordered transports like MQTT or intermittent HTTP long-polling.

Rendering diagram...

#Failure Modes & Edge Cases in CRDT Conflict Resolution

The theoretical guarantees hold only if the implementation respects three invariants: causal delivery, tombstone retention until safe garbage collection, and monotonic dot allocation. Break any of these and convergence silently fails without throwing an exception — the hardest kind of bug to catch in staging.

  • Tombstone accumulation: OR-Sets and RGAs never truly delete data at the CRDT layer; they mark it removed. A document edited heavily over two years can carry more tombstone metadata than live content. You need a causal stability protocol — once every replica has acknowledged an operation, and no replica can generate a new concurrent op against it, tombstones can be pruned. This requires tracking a stable version vector across all known replicas, which is operationally expensive for large, high-churn replica sets.
  • Dot counter reuse after app reinstall: if a client’s local storage is wiped and the replica ID is regenerated, but the same physical device resumes sending ops with a fresh counter starting at zero, you get silent data corruption if the old replicaId’s ops re-enter circulation. Replica IDs must be immutable and persisted outside app-clearable storage (e.g. Keychain, not localStorage).
  • Causal gap stalls: an op-based CRDT that receives operation 12 before operation 11 (out-of-order delivery) must buffer 12 until 11 arrives, or fall back to a full state resync. Under lossy mobile networks this buffering can grow unbounded if the transport has no retry-with-backoff guarantee.
  • Merge amplification under high fan-out: a document shared across 500 replicas generates O(n²) potential concurrent-edit pairs during a reconnection storm (e.g. after a regional outage). Batching merges server-side into a single lattice join before broadcasting, rather than pairwise replaying, is mandatory past a few dozen concurrent replicas.

#Scaling and Security Trade-offs

CRDT conflict resolution removes coordination cost but does not remove state cost — that trade-off has to be made explicit to stakeholders before adoption, because the failure signature looks completely different from a traditional locking database.

  • Storage growth vs data fidelity: RGA-based text CRDTs retain full edit history as tombstoned nodes. Accepting this overhead buys you real-time collaborative editing with no server-side locking; rejecting it (via periodic snapshot-and-truncate) means you lose the ability to correctly merge very old offline branches.
  • Bandwidth vs merge complexity: state-based CvRDTs are simpler to implement and audit but transmit full state on every sync — unacceptable for documents beyond a few hundred KB. Delta-state CRDTs cut bandwidth by 80-95% in typical field-edit workloads but require careful causal-context bookkeeping to avoid double-application.
  • Security surface of causal metadata: replica IDs, dots, and vector clocks are typically transmitted unencrypted alongside payload data because the merge server needs to inspect them. This leaks device fingerprinting information (edit frequency, device count per user) unless you rotate replica IDs per session and enforce TLS for all sync transport, per standard CRDT formalism from INRIA’s research on convergent replicated data types.
  • Consensus-free writes vs authorisation enforcement: because CRDT conflict resolution deliberately avoids a coordinator, you lose the natural checkpoint where you’d normally enforce write-level authorisation. Access control has to be applied at the operation-generation boundary (client-side, before the op is even created) and re-validated server-side against a signed capability token, not inferred from merge order.
  • Multi-region latency vs consistency window: CRDTs give you availability under partition (AP in CAP terms) at the cost of a bounded staleness window between regions. For workloads that need strict linearisability — financial ledgers, inventory reservation — layer a CRDT-backed cache in front of a strongly consistent ledger rather than using CRDTs as the system of record.

None of this makes CRDT conflict resolution a universal replacement for consensus-based replication — it is a targeted tool for the specific shape of problem where availability under partition matters more than immediate global ordering. Field-service tools, collaborative editors, and offline-capable mobile apps sit squarely in that shape. Ledger systems, inventory reservations, and anything requiring a single source of truth at write time do not, and forcing a CRDT model onto them tends to produce the exact class of silent divergence bugs the data structure was designed to eliminate in the first place.

Reader Support: KBY Technologies is an independent engineering editorial. We may earn a commission from affiliate links when you purchase infrastructure, software, or tools through our recommendations. This helps fund our research and labs.

Reader Interaction

Comments

Add a thoughtful note on Architecting CRDT Sync for Offline-First Apps. Comments are checked for spam and held for moderation before appearing.

Loading comments...

Loading anti-spam check...

Eleanor Hayes

Written By

Eleanor Hayes

Dr Eleanor Hayes is a veteran cryptography researcher and enterprise security architect specialising in zero-trust network implementations. Having spent a decade securing critical national infrastructure, she now designs identity-aware proxy perimeters and mutual TLS topologies for highly distributed environments. She holds multiple GIAC certifications and regularly consults on mitigating advanced persistent threats within microservice ecosystems.