Data Sync & Conflict Resolution
The sync problem
In an offline-first app, the same data lives in two places: the phone and the server. Users edit offline; multiple devices edit the same thing; the network drops mid-update. Synchronisation keeps these copies consistent, and conflict resolution decides what happens when they disagree. This is one of the hardest parts of mobile system design.
Two directions of sync
- Pull (download) — get the latest server data into the local DB.
- Push (upload) — send local changes (made offline) up to the server.
The outbox pattern for offline writes
When the user makes a change offline, you can’t call the server yet. The robust pattern: write the change to the local DB immediately (so the UI updates), and add it to an outbox (a queue of pending operations). A background worker drains the outbox when connectivity returns, retrying failures.
// mark a local change as pending, then sync later
post.syncStatus = SyncStatus.PENDING
postDao.upsert(post)
// WorkManager job: send all PENDING items, mark SYNCED on success, retry on failure
Efficient syncing: delta sync
Don’t re-download everything each time. Use delta sync: ask the server “what changed since my last sync timestamp/cursor?” and apply only the changes. This saves data, battery and time — essential on mobile.
Conflict resolution strategies
When the local and server versions both changed, you must choose:
- Last-write-wins — the most recent edit (by timestamp) wins. Simple, but can lose data.
- Server-wins / client-wins — one side is always authoritative.
- Merge — combine changes field-by-field when they don’t overlap.
- Ask the user — surface the conflict and let them choose (for important data).
- CRDTs — advanced data types that merge automatically (used in collaborative apps like notes).
Pick based on the data: a draft note might merge; a bank balance must be server-authoritative.
Idempotency: safe retries
Because you retry network calls, the same write might reach the server twice. Make operations idempotent (give each a unique id/key) so the server can ignore duplicates — otherwise a retry could create two identical posts or double-charge.
Common mistakes
- No conflict strategy — silent data loss.
- Full re-sync every time instead of delta sync (slow, data-hungry).
- Non-idempotent writes causing duplicates on retry.
- Blocking the UI on sync instead of doing it in the background.
Summary: Sync pulls server changes and pushes local ones. Use an outbox + background worker for offline writes, delta sync to transfer only changes, a conflict strategy appropriate to the data (last-write-wins, server-wins, merge, or ask the user), and idempotent operations so retries don’t duplicate.