Sync Conflicts in Mobile Apps: Strategies That Don't Lose Data
How to resolve offline sync conflicts in mobile apps: last-write-wins, versions, merges, CRDTs, append-only data, server authority, clocks and testing.
Full-Stack, Mobile & AI Engineer
Two phones, one account, no signal on either. On the first, the user renames a workout plan. On the second, they delete it. Both phones come back online. What should the server do?
There's no universally correct answer, which is exactly why offline sync conflict resolution has to be designed rather than discovered in production. This guide covers the strategies that work in mobile apps, how to choose one per type of data, the clock problems that trip teams up, and how to test conflicts before users find them.
Key takeaways
- Conflicts are a product decision as much as a technical one. Decide per type of data what "correct" means.
- Avoid conflicts by design: append-only records (events, logs, completed sessions) never conflict.
- Last write wins is fine for low-stakes fields, especially when applied per field rather than per record.
- Versions (optimistic concurrency) detect conflicts so you can merge or ask the user instead of silently overwriting.
- Don't trust device clocks for ordering; use server-assigned versions or sequence numbers.
Why conflicts happen
An offline-first app lets users change data without a connection. A conflict occurs when the same record changes in two places (two devices, or a device and the server) before they sync. Without a rule, the last one to sync silently overwrites the other, and users experience it as "the app lost my changes".
The strategies
| Strategy | How it works | Best for | Risk |
|---|---|---|---|
| Append-only | Never edit; add new records (events, sessions) | Logs, workouts, messages, transactions | Needs compaction or projections for "current state" |
| Last write wins (record) | The latest change replaces the whole record | Rarely the best choice | Loses unrelated edits to other fields |
| Last write wins (field) | Each field keeps its latest value | Profiles, settings, simple forms | Can combine values into an odd state |
| Optimistic concurrency | Each change carries the version it was based on; mismatches are rejected | Important records edited in several places | Needs a resolution step |
| Merge operations | Sync operations ("add item", "increment"), not values | Lists, counters, sets | More design work |
| CRDTs | Data structures that merge automatically and consistently | Collaborative text, shared documents | Complexity and storage overhead |
| Server authority | The server decides; devices accept its answer | Payments, bookings, inventory, permissions | Offline edits may be rejected |
| Ask the user | Show both versions and let them choose | Rare, high-value edits | Interrupts users; use sparingly |
Design to avoid conflicts
The cheapest conflict is the one that can't happen:
- Model actions as facts. "Completed a workout at 07:12" is an event, not an editable document. Two devices can both add events without conflicting.
- Split records so independent fields live in different places. A user's display name and their notification settings don't need to be one row.
- Keep shared mutable data small, and give it clear ownership (only the owner edits it).
Optimistic concurrency in practice
Give each record a version number that the server increments on every change. Devices send the version their edit was based on:
UPDATE plans
SET name = $1, version = version + 1, updated_at = now()
WHERE id = $2 AND version = $3; -- $3 is the version the device started fromIf the update affects zero rows, someone else changed the record first. The server returns the current version, and the app either merges automatically (if the changes touch different fields) or applies your rule for that data type.
Deletes: use tombstones
If a record is deleted while another device is offline, simply removing the row means that device will happily recreate it on its next sync. Instead, mark the record deleted (a tombstone with a deletion version) and sync that. Decide your "edit vs delete" rule explicitly: for example, a delete wins unless the edit happened after it was synced.
The clock problem
Device clocks are wrong more often than you'd think: set manually, drifting, or in the wrong time zone. Ordering changes by device timestamps leads to "newer" edits losing to older ones.
- Use server-assigned versions or sequence numbers for ordering.
- Record device timestamps for display, not for deciding conflicts.
- If devices must order events among themselves, use logical clocks (such as hybrid logical clocks) rather than wall time.
From the field: For MOVE, a fitness app with 100K+ active users, I built offline-first workout tracking with cache-first sync so sessions persist through network drops with zero data loss. For data like workout sessions, append-only design is the natural fit: a finished session is a fact, and facts don't conflict, which removes the hardest class of sync bugs from the data users care about most.
Choosing rules per data type
A practical table for a typical app:
| Data | Rule |
|---|---|
| Completed sessions, messages, check-ins | Append-only |
| Profile name, avatar, preferences | Last write wins per field |
| A shared shopping or packing list | Merge operations (add, remove, check) |
| Notes edited on several devices | Optimistic concurrency, auto-merge fields, otherwise keep both |
| Bookings, payments, stock levels | Server authority |
Testing conflicts
- Two devices (or simulators) signed in to the same account, both offline, editing the same record differently, then reconnecting in each order.
- Edit on one device and delete on another.
- A device with its clock set an hour wrong.
- A long-offline device with many queued changes against a server that moved on.
Automate the core cases as integration tests against your sync endpoints, because conflict rules are easy to break with an innocent-looking change.
FAQ
What is the best way to resolve sync conflicts in a mobile app?
There isn't one rule for all data. Use append-only records for events, last-write-wins per field for simple settings, optimistic concurrency with merging for important shared records, merge operations for lists, and server authority for payments and bookings. Designing data to avoid conflicts is the most effective strategy.
Is last write wins good enough?
For low-stakes fields like preferences, usually yes, especially applied per field. For records where edits matter, it silently discards one user's changes. Use versions to detect those conflicts and merge or ask instead.
Should I use CRDTs in my React Native app?
Only if you need real-time collaborative editing of the same content, such as shared documents or whiteboards. For most apps, simpler strategies (append-only data, per-field last-write-wins, optimistic concurrency) cover the cases with far less complexity.
Why shouldn't I use device timestamps to order changes?
Device clocks can be wrong, manually changed or drifting, so a newer edit can carry an older timestamp and lose. Use server-assigned versions or sequence numbers for ordering, and keep device timestamps for display.
Next steps
If your app's sync occasionally loses or duplicates data, I can review the rules, design conflict handling per data type and add tests that keep it correct. Get in touch, or see how I work as a React Native developer.
Sources
- WatermelonDB sync, WatermelonDB
- Conflict-free replicated data types, crdt.tech
- Optimistic concurrency control (concepts), PostgreSQL docs
- Local-first software, Ink & Switch
Related
Offline-First React Native: Lessons from an App with 100K Users
How to build offline-first React Native apps that never lose data: local-first storage, an outbox for writes, sync, conflicts and testing bad networks.
WatermelonDB vs SQLite vs Realm in 2026
WatermelonDB vs SQLite vs Realm for React Native in 2026: performance, sync, the end of Atlas Device Sync, and which local database to choose.
MMKV vs AsyncStorage vs SQLite: Picking Local Storage
MMKV vs AsyncStorage vs SQLite for React Native in 2026: speed, data shape, encryption and size limits, which to use for what, and how to migrate.
