Offline-first is a product decision
Queueing writes is the easy half. What happens when a representative returns from two days without signal is a product question, and it usually gets answered by whoever wrote the sync code.
Engineering practice3 min read
A representative comes back from two days on a rural route with forty-one records on the phone that the server has never seen. Several of them contradict things that happened in the office while the phone was out of contact. What the software does in the following ten seconds is a product decision. In most programmes it gets made by whoever is writing the sync layer on a Thursday afternoon, which is how it ends up being last write wins.
Queueing is the easy half
An outbound queue with idempotency keys, ordered delivery and retry with backoff is a solved problem. It takes a competent engineer a sprint and it is not where the risk sits. The risk is in what the queue contains.
Three kinds of thing, and only one is safe
- Events. Something happened at a time and a place. A visit occurred, a delivery was signed for, a meter was read. Events do not genuinely conflict. They only need ordering, and two devices reporting different events is not a disagreement.
- Mutable records. A customer's address, an opportunity value, a stage on a pipeline. Two people can change these independently and both changes can be plausible. This is where real conflict lives.
- Claims on something scarce. Stock allocation, an appointment slot, a discount that requires approval. These cannot be resolved on the device at all, because the device has no way of knowing what else has been claimed since it lost signal.
Most of the design work is moving as much as possible into the first category. A visit is an event. A discount modelled as a field on a customer record is a conflict waiting to happen; the same discount modelled as a request that has not yet been granted is a queue with an answer at the end of it. The second version is more honest about what the representative actually did, which was ask.
Last write wins is a decision to lose data quietly
Field-level merge with timestamps handles most mutable-record cases well enough, and the elaborate machinery of vector clocks is usually more than the problem deserves. The rule that matters is different: never discard the losing version. Keep it, surface it, and let a human look at it.
Genuine conflicts are rare, a fraction of a per cent of records in the systems we have measured. That is cheap to resolve by hand and expensive to resolve silently, because a silent resolution has no error rate anyone can see. The person best placed to decide which address is correct is nearly always the person who typed one of them.
The first screen after two days offline
It should not be a spinner. It should be a reconciliation, and it should appear before the working day starts rather than at the end of it.
Three things belong on it. What went up successfully. What was rejected, with the reason in plain language and a way to fix it. And what changed while the device was away that affects today: a cancelled appointment, a customer moved onto credit hold, a price that is no longer valid. If three of forty-one records were rejected, that has to be known on Wednesday morning, not discovered at month end when the commission figure comes up short.
Staleness has to be visible
Reference data goes off. A price list cached on Monday, a stock position, a customer's credit status. The cheap and effective answer is to show the age of the data wherever being wrong has a cost, and to mark anything derived from it as provisional until the device has reconnected. A quoted price that carries "based on Monday's list" is a professional answer. The same number with no qualifier is a promise the business may not want to keep.
Offline-first costs real money on the client, and it forces a product owner to make decisions they would rather delegate: which records can be edited away from the network, which cannot, and who arbitrates when two versions arrive. That is the argument for doing it deliberately. Skip the argument and the same decisions still get made, quietly, in the sync layer, by someone with no visibility of what a wrong answer costs in the field.
More on engineering practice
All writingNovember 2025
What fifteen years of maintenance taught us about design
Fifteen years in, the systems we still maintain have taught us more than the ones we launched. Some early decisions aged quietly well. Others cost us for a decade.
August 2025
Senior engineers in a client's team, and the reporting line
Embedded engineering works when one question is settled in writing: who sets priority. Ambiguity there produces most of the friction people blame on culture or communication.
July 2023
Test automation as a service, and when it stops paying
A test suite is code, with the same rot and the same maintenance cost. Where automation earns its keep, which tests are worth deleting, and how to tell when a suite stopped paying.
Talk to our engineering team
Tell us what you need built, modernised or maintained. We will tell you whether we are the right firm for it and what it costs.