Offline Synchronization and Conflict Resolution

Offline synchronization lets users work before a server can acknowledge their changes. Store the local intent durably, give each operation a stable identity, and reconcile it against authoritative state when communication returns.

Separate saved locally, queued, transmitting, accepted, conflicted and failed. A spinner disappearing should not imply that the server has committed a change when the device merely wrote it to its own database.

TL;DR

Quick Example

This complete Python model uses invented server state, a version precondition and an operation receipt. It is single-process memory, not a durable sync service. Reusing an operation ID with different content is rejected.

The receipt and mutation must be persisted consistently in a real implementation. Otherwise a crash between them can lose duplicate protection. Authorization, concurrency, receipt expiry, durable outbox and network behavior are deliberately outside the model.

Core Concepts

Local source and outbox

An offline-first architecture exposes useful local data while coordinating network updates through the data layer. Android’s guidance discusses queued writes, retries and conflict resolution. Decide which data is authoritative for each decision and which changes are only provisional. Offline-first data layer

Operation identity

A stable ID identifies one intended mutation, not merely one network attempt. Scope it to the relevant actor/resource and bind it to the payload. Preserve receipts according to a defined lifetime so a delayed retry cannot become a new effect accidentally.

Version preconditions

Conditional writes can reject stale updates rather than silently replace newer state. HTTP If-Match provides a standard precondition mechanism when the API uses entity tags; the Python integer version is only an illustration. HTTP conditional requests

Choose a conflict policy from the data

Last-write-wins can be simple but may discard meaningful edits and depends on how order is established. Device clocks are not automatically trustworthy ordering authorities. Field merge, domain-specific operations, explicit review or a suitable convergent data type may fit different requirements.

Keep the conflict UI close to the user’s task. Explain which local change is preserved, what the server currently knows, and what action resolves the difference. Do not replace a failed upload with the server’s value and silently erase the user’s work.

If a domain operation cannot be accepted offline, let the user prepare a draft or show a pending request with clear limits. A local “reserved” badge should not promise inventory the server has never allocated.

Make the queue durable and bounded

Persist the local state change and outbox entry through a consistent transaction where the storage design supports it. Store the operation identity, target, base version, payload, creation context, attempt state and required account scope. Keep credentials outside the payload.

Use bounded retries with backoff and classify permanent failures separately from temporary network loss. Connection availability is only a signal to attempt work; it does not prove the server is reachable or that authorization remains valid.

Define order constraints explicitly. Some operations depend on earlier acknowledgments; others may safely proceed independently. A single failed record should not block unrelated work forever, but reordering dependent writes can corrupt meaning. Record why work is paused and give operators or users an appropriate recovery route.

Bound queue size, age and retained data under a documented policy. Surface work that needs attention rather than discarding it just to reduce storage. Account removal and logout require a deliberate choice for unsynchronized work and must prevent another account from inheriting it.

Test interruption and truthful status

Exercise network loss before send, during upload, after server commit but before receipt, during local acknowledgment, and during conflict resolution. Restart the process between steps using durable test storage in the real integration. Verify intended effect counts and preservation of local intent.

Include two devices, stale versions, repeated IDs, deletion/recreation, schema changes, expired credentials, and a long-offline client. The in-memory example cannot establish crash safety or multi-device correctness, but it exposes the boundaries those tests must cover.

Measure queue age, conflicts, retries and accepted outcomes without storing sensitive payloads in ordinary telemetry. Display sync status from persisted evidence. “Saved locally” can be a useful, honest success state while “synced” remains pending.

Best Practices

Preserve intent through failure

Keep local edits and conflict evidence until the defined acceptance or discard decision. Avoid using an error handler as an accidental data-loss path.

Bind receipts to operations

Check identity, actor/resource scope and payload consistency. A reused ID must not authorize a different mutation.

Verify the durable boundary

Crash/restart tests and concurrent server tests matter more than repeatedly running the single-process model. Keep its limits visible in acceptance evidence.

Common Mistakes

Bad: Calling a locally saved record synchronized.

Correct: Represent local durability and remote acceptance as different states.

Bad: Resolving every conflict by newest device timestamp.

Correct: Choose a domain policy and test clock, lost-edit and deletion cases.

Bad: Retrying a write with a fresh identity every time.

Correct: Preserve the intended operation ID and implement durable duplicate handling.

FAQ

Is last-write-wins always wrong?

No. It can fit data whose loss/ordering policy is acceptable, but define the authority and test its consequences.

Does an online indicator mean the queue is synchronized?

No. Server reachability, authentication, acceptance and local acknowledgment remain separate.

Was crash-safe synchronization tested here?

No. The example checks repeat/version logic in memory; durable storage, concurrency, network and restart tests remain required.

Related Topics

References