TL;DR / When multiple clients edit data offline and reconnect, conflict resolution strategies determine how to merge divergent state into a consistent result without losing user intent.
How It Works
┌────────────┐ ┌──────────────┐
│ Client A │ │ Local │
│ (offline) │───────→│ Edits v1→v2 │───┐
└────────────┘ └──────────────┘ │
│
│ ┌────────────┐ ┌────────────┐ ┌────────────┐
│ │ Server │ │ Merge │ │ Resolved │
┌───→│ Reconnect │───────→│ Strategy │───────→│ State v4 │
│ └────────────┘ └────────────┘ └────────────┘
│
┌────────────┐ ┌──────────────┐ │
│ Client B │ │ Local │ │
│ (offline) │───────→│ Edits v1→v3 │───┘
└────────────┘ └──────────────┘
Offline conflict resolution is the problem of reconciling divergent state that arises when multiple clients modify shared data without network connectivity, then attempt to synchronize. The core challenge is that each client has no knowledge of the other's mutations during the disconnected window, so naive approaches like "last write wins" destroy user intent.
The simplest strategy is last-write-wins (LWW). Each mutation carries a timestamp, and the most recent write overwrites earlier ones. LWW is trivial to implement but semantically destructive -- if Client A edits a document title and Client B edits the body, LWW forces one client's entire state to overwrite the other. It works acceptably only for single-field atomic values.
Operational Transformation (OT) was the original solution for collaborative text editing, powering Google Docs for years. OT works by transforming operations against each other: if Client A inserts at position 3 and Client B deletes at position 1, Client A's insert index must be adjusted to account for the deletion. The transform function takes two concurrent operations and produces adjusted versions that can be applied in either order to reach the same final state. OT's complexity explodes with operation types -- every pair of operation types needs a transform function, and the server typically acts as a centralized sequencer.
CRDTs (Conflict-free Replicated Data Types) sidestep transformation entirely by encoding conflict resolution into the data structure itself. A G-Counter, for example, maintains per-replica counts that only increment, merging by taking the maximum per replica. For text, sequence CRDTs like Yjs or Automerge assign globally unique, totally ordered identifiers to each character, making concurrent inserts at the same position deterministic without coordination. CRDTs guarantee eventual consistency: all replicas that have seen the same set of operations converge to the same state regardless of operation order.
Three-way merge is the strategy used by version control systems and works well for structured documents. Given a common ancestor version and two divergent branches, a three-way merge algorithm identifies which fields each branch modified relative to the ancestor. Non-conflicting changes (different fields) merge automatically. True conflicts (same field, different values) require resolution -- either programmatic heuristics or user intervention.
Vector clocks and version vectors provide the causality tracking necessary for any sophisticated merge strategy. Each node maintains a vector of logical clocks, one per replica. When comparing two versions, the vector clock relationship reveals whether one causally precedes the other (one vector dominates) or they are concurrent (neither dominates). Concurrent versions are the ones requiring conflict resolution.
In practice, offline-first applications use a combination of these techniques. IndexedDB or SQLite stores a local operation log with vector clock metadata. On reconnection, the client exchanges its operation log with the server, which identifies concurrent operations using causal ordering and applies the chosen merge strategy. The server then broadcasts the resolved state to all connected clients.
The choice of strategy depends heavily on data semantics. Shopping carts use add-wins sets (a CRDT variant). Collaborative documents use sequence CRDTs. Configuration objects use field-level three-way merge. Financial records often use explicit conflict queues requiring human resolution rather than automatic merging.
Conflict resolution also needs convergence verification -- a mechanism to confirm all replicas eventually agree. Anti-entropy protocols periodically compare Merkle tree hashes of replica state, triggering resynchronization when divergence is detected.
Gotchas
- Last-write-wins destroys concurrent edits silently -- users lose work with no indication, making it unsuitable for multi-field documents or collaborative scenarios
- OT requires a central sequencer -- pure peer-to-peer OT is notoriously difficult to implement correctly; most implementations funnel through a server to establish operation order
- CRDTs trade memory for correctness -- tombstones from deleted elements persist in the data structure, causing unbounded growth without periodic garbage collection (compaction)
- Wall-clock timestamps are unreliable for ordering -- client clocks drift; use Lamport timestamps or vector clocks for causal ordering instead of
Date.now() - Conflict resolution must be deterministic across replicas -- if two replicas resolve the same conflict differently, you get permanent divergence that no amount of syncing will fix