Two devices changed the same record. What exactly needs syncing?
A phone and tablet edit one shopping-list record. How to preserve a pending local edit through revision checks, server conflicts, user choice and retries after a lost response.
- Published
- October 7, 2026
- Verified
- October 6, 2026
Two devices changed the same record. What exactly needs syncing?
A shared shopping list says: one carton of milk. Someone changes the quantity to two on their phone, but the phone is offline. At home, the same list is open on a tablet. The quantity changes to three there, and the server accepts it. Later, the phone reconnects and sends its saved “two”.
The system now has three numbers: the original one, the three confirmed by the server, and the two that is still only on the phone. Sending the phone’s value is easy. What gives it the right to replace a change the server has already accepted?
If both devices end up showing two, their copies agree. But the decision made on the tablet has disappeared without warning. If both show three, the person using the phone may think their own change was never saved. “Synchronized” describes matching copies in both cases. It does not tell us whether the result is right for the person using them.
Losing the network is not the cause of the conflict. Two open screens can read the same record and change it almost simultaneously while both are online. Being offline merely gives a device more time to live with its own account of what happened.
What the device needs to keep
The list initially arrives from the server with a revision number. Let the record for one carton of milk have revision 17. Both devices read that revision.
After the phone changes the quantity, two distinct things exist there: the intention to set the quantity to 2, and the last confirmed value it received from the server, 1. The screen may show two immediately; that is reasonable for a shopping list. But the person needs to know that the change is stored on this device and still awaits delivery. Otherwise a local edit looks like a decision made by the whole system.
Flutter’s offline-first guide describes writing locally before sending the change and marking data that still needs synchronization. That is useful, but a synchronized: false flag alone is not enough when another device can edit the same record. The flag says the work is unfinished. It neither records the revision on which the decision was based nor chooses what happens when edits collide.
The phone therefore needs to retain more than the number two. It needs revision 17, the basis on which that number was chosen, and the delivery status. Without a base revision, the chosen contract cannot distinguish a current edit from one made against an old value.
What the server checks
The tablet sends “set the quantity to 3 based on revision 17” first. The server finds that the record is still at revision 17, accepts the change and creates revision 18. Then the phone arrives with two, also based on revision 17. If the product does not allow an edit to be silently lost, the server does not accept two over the confirmed three. It reports the mismatch and returns the current value with revision 18.
That does not make three permanently “more correct” than two. The server has established a narrower fact: the phone made its decision without knowing about the tablet’s change. No rule has yet authorized the server to choose between those two intentions on the person’s behalf.
Checking the revision and writing the new value must be one operation. Reading the revision first and updating the row later leaves a gap in which another edit can arrive. For a list stored in PostgreSQL, the condition can be part of the UPDATE itself:
UPDATE shopping_items
SET quantity = :new_quantity,
revision = revision + 1
WHERE id = :item_id
AND owner_id = :owner_id
AND revision = :base_revision
RETURNING quantity, revision;
The revision condition prevents this update from applying to a different revision of the row. RETURNING supplies a value only when a row was updated, as documented for PostgreSQL UPDATE. If no row changed, the server still has to find out why: an outdated revision, a deleted record, or missing access. A zero-row update is not, by itself, a message ready for the screen.
The revision number orders confirmed changes to this record. It is not the phone’s clock or a global counter for every change in the system. Multiple independent server writers make the problem harder; this article considers one authoritative server contract for a record, not a multi-master replication design.
At the HTTP layer, a strong ETag with If-Match can express the same condition. When it does not match, RFC 9110 specifies 412 Precondition Failed, apart from the case where the server recognizes that the request has already succeeded. A custom API that carries the base revision in its request body might instead define, for example, a 409 response with the current value. These are alternative ways to express the condition, not two mandatory layers of checking.
A conflict is not a network failure
Network loss, a successful save and a revision conflict call for different client behavior.
When the network is unavailable, the local edit waits to be sent again. After a successful save, the client keeps the new revision and clears the pending label. On conflict, it cannot simply replace the local two with the server’s three: the server kept its record, but it did not undo the decision made on the phone.
The screen needs an honest choice. It might say: “On this device: 2. In the shared list: 3. Keep 3 or replace it with 2?” Choosing two is a new edit based on revision 18, not a retry of the old request based on 17. The server may accept it as revision 19, provided the record has not changed again meanwhile.
For a Flutter screen, this is more than another error message. If the app promises to preserve offline edits, the local two must survive both refreshing the list and restarting the app. When a conflict arrives, writing the server’s three over the only local row and then offering a choice is too late: the two is gone. The confirmed server version and the unresolved local edit have to be kept separately. “Keep 3” discards the local edit; “replace it with 2” creates a new submission with a new base revision.
A third device does not demand a new principle. It adds another writer, while each edit still needs to be checked against the revision its writer actually saw. The record can even change while the phone is displaying the conflict. A person’s choice does not exempt the next submission from another server-side check.
A choice like this may be acceptable for a simple list. If it appears on every other interaction, reconsider the record’s granularity or the product flow; polishing a conflict dialog will not fix a poor boundary. Yet hiding it behind “Could not sync” is not useful either. That tells the person neither what happened to their edit nor what they need to decide.
Can the changes be merged automatically?
Sometimes. The phone might change the quantity while the tablet clarifies the item’s name. If the server retains the base revision and knows which fields each client changed, it can try to combine those edits. Different fields alone do not prove that doing so is safe. Name and quantity may participate in one product rule, and deleting the item on one device changes the meaning of an edit from the other.
A merge needs rules belonging to the shopping list: which changes are independent, what deletion means, and which combinations are valid. For two versus three cartons, there is no universal arithmetic. 2 + 3 would produce five cartons, although nobody requested five. Taking the larger number is just as arbitrary.
Another deliberate policy is last-write-wins: a later write replaces an earlier one. Cloud Firestore explicitly describes that behavior for multiple offline changes to one document. It can fit data where losing an earlier value is acceptable or easy to repair. If every quantity change matters, however, the rule should be a product decision rather than an accidental property of the chosen platform. Device clocks will not turn it into an arbiter of meaning.
A retry is not a new edit
Consider a different outcome, with no tablet edit. The phone sends two based on revision 17; the server accepts it as revision 18, but the response is lost. The phone does not know the result. If it resends the request with base revision 17, the server can now see 18 and report a conflict. The person would be asked to resolve a conflict with their own successfully saved change.
Revision comparison handles different edits colliding. It does not recognize a retry of the same edit. The client can give an edit a stable identifier; the server can remember the outcome and the original parameters under that identifier. A retry then receives the recorded result, while a new decision gets a new identifier. The pattern is discussed in Amazon Builders’ Library.
There is a boundary here too. The identifier and the result of the change must be recorded atomically. If the offline queue retains edits longer than the server retains their identifiers, safe retry is no longer guaranteed. No such server has been built for this article; this is a condition an implementation would have to test.
Questions worth asking in review
For any record edited from several devices, the interval between background refreshes is less important than four answers:
- What on the screen is confirmed by the server, and what is only stored locally?
- Which revision was an outgoing edit based on, and where is that revision checked atomically?
- Who chooses the result of a collision: a server rule, a domain-specific merge, or the person using the app?
- How is a retry after a lost response distinguished from a new decision?
Push notifications and frequent synchronization may shorten the time in which copies differ. They do not remove the possibility that two devices have already changed the same base. A polished Flutter screen cannot recover an edit after the server has silently replaced it either.
Not every app needs an operation log, a general-purpose merge engine and a separate conflict screen. Some can prohibit offline writes. Others need record revisions and a clear conflict response. Where losing an older value is acceptable, last-write-wins may be an honest policy. The choice depends on the cost of losing a person’s decision, not on how fashionable the synchronization mechanism is.
This is a contract for one illustrative record, not the result of an experiment. No server or Flutter client was run; concurrent sequences were not tested in code, and the usability of the proposed choice was not studied. The central review question remains useful: which revision was a local decision based on, and who is allowed to resolve the difference? Without an answer, synchronization can neatly spread a lost edit to every device.
ArkTelos: official website · news EN · новости RU
ArkTelos Lab: journal · Telegram EN · Telegram RU
Result boundaries
- Analytical example for one record with one authoritative server. No working server or Flutter client was built; concurrent sequences were not tested in code, and the proposed choice was not studied with users.
Telegram edition