A retail counter does not get to assume the internet is up. The card network blips, the DSL drops, a staff member knocks the router — and the queue does not care. If your point-of-sale app freezes every time connectivity wobbles, you lose sales and you lose the cashier's trust. While building a retail POS that had to keep selling through dropouts, we settled on a handful of rules that kept the data sane once the connection came back.
The problem: two tills, one network drop
Picture two tills in the same shop. The network drops for ninety seconds. Both keep selling against their local copy of stock. When they reconnect, they each try to push their version of "what happened." Naive last-write-wins throws away real sales. A blocking "wait for the server" approach stops the shop. You need something in between: keep selling locally, then merge deterministically.
Rule 1: make every sale idempotent
Generate the transaction ID on the client, not the server. A UUID minted at the till means a sale can be retried any number of times without being counted twice. The server treats an incoming ID it has already seen as a no-op and returns the stored result. This one decision removes a whole category of "the payment went through but the app resent it" bugs, and it makes your sync endpoint safe to call aggressively.
Rule 2: treat stock as events, not absolute counts
Do not sync the number 42. Sync the events that produced it: sold 3, received 10, adjusted -1. Absolute counts from two offline tills overwrite each other; append-only events from two tills simply interleave. The current quantity becomes a reduction over the event log, which also gives you a free audit trail — useful when an owner asks why the count moved.
Rule 3: resolve the few real conflicts deliberately
Most "conflicts" disappear once stock is event-based, because addition is commutative. What remains are genuine business decisions: two tills sell the last unit of an item. That is not a data problem you can merge away with conflict-free replicated data types alone — it is a policy. We let both sales stand, flag the item as oversold, and surface it for the manager, because in practice a quick restock or refund beats blocking the sale at the counter.
What we would not do again
Early on we tried to keep a live socket open and "just reconnect." It made the happy path feel instant and the unhappy path impossible to reason about. Offline-first — local write, queue, reconcile — turned out to be simpler to build and far easier to explain to the people who actually stand at the till. If connectivity is unreliable where your software runs, design for the drop first and treat the online case as the optimisation, not the other way round.
