DEV Community

Ahmed ElFirgany
Ahmed ElFirgany

Posted on

Offline-first sync: when two edits collide, who wins?

Field users lose signal in the middle of a task. The app still has to work. Then it has to catch up without losing a record.

We faced this in a real-estate CRM app that handles 100K+ leads a month. The rebuild made it about 30% faster.

Our take

Three places hold the truth:

  • a local store on the phone
  • an outgoing sync queue
  • the server

The UI updates optimistically from the local store. When the network is back, each queued change reconciles or rolls back. The server has the final word.

That works well for one user on one device.

Where it hurts

Two people edit the same record while both are offline.

Both changes reach the server later. One of them has to lose, or the two have to merge.

Options we weighed:

  • last write wins. Simple, but it silently drops someone's work.
  • field-level merge. Fairer, but you need to track changes per field.
  • ask the user. Safest, but it interrupts the flow.

Your turn

Two offline edits collide. Who wins in your app, and how do users find out?

Top comments (3)

Collapse
 
ahmedznouda profile image
Ahmed ZNOUDA •

My vote goes to field-level merge as the default, with "ask the user" kept for one case only: two people changed the same field to different values.

The reasoning: in a CRM, most collisions are not real conflicts. One person updates the phone number while another changes the status. Last write wins throws one of those away for no reason, and nobody notices until a lead goes cold.

For telling users, I would avoid a blocking dialog. A small note on the record ("status changed by Sara while you were offline") plus a per-field history is usually enough, and it keeps field staff moving.

The last write wins is fine for one thing: fields where only the latest value matters, such as "last contacted." How do you track per-field changes on the phone, a timestamp per field, or a change log?

Collapse
 
saqrelfirgany profile image
Ahmed ElFirgany •

Thanks, the phone versus status example makes the rule concrete. A change log works better than a timestamp per field. Each local edit writes a row: record id, field, old value, new value, base version. The server keeps a version per field. A conflict means the base version is behind and the values differ. Phone clocks drift, so device timestamps alone are risky. They are fine for fields like last contacted, where last write wins anyway.

Collapse
 
ahmedznouda profile image
Ahmed ZNOUDA •

A change log with a base version per field is the cleaner design, agreed, and you're right about clocks: a phone that's a few minutes off would quietly win or lose every conflict.

Two small things that tend to come up with a log like that:

  1. Give each row a client-generated id, so if the upload times out after the server applied it, the retry is recognized and not applied twice.
  2. Squash the log before sending: three offline edits to the same field can go up as one change, with the base version of the first edit and the value of the last. Less to sync, and only one conflict to show if there is one.