Skip to main content
KillaQ
Mobile5 min read

Offline-first is not a feature: it is an architecture

How to design an idempotent write queue so an app keeps working when the network drops.

Written by KillaQ

In a lift, on a train, or anywhere with patchy coverage, the network is not an edge case — it is the normal condition. An app that assumes permanent connectivity does not fail occasionally; it fails exactly when the user needs it most.

Treating offline as an error state is the mistake

The usual implementation wraps every request in a try/catch, shows "no connection", and loses whatever the user just typed. That is not supporting offline. That is announcing that offline is unsupported.

Offline-first inverts the relationship. The source of truth for the user is the device. The network is a synchronisation mechanism that may be slow, may fail, and may arrive out of order.

The outbox queue

The core pattern is a persistent write queue:

  1. The user's action is applied to local state immediately and persisted.
  2. The intent is enqueued as an operation with its own identifier.
  3. A background worker drains the queue when the network returns, with retries and exponential backoff.
  4. The server response reconciles local state.

The important part is that step 1 never depends on step 3.

Idempotency, or duplicates

If the client resends an operation because it never got the response — not because the server never got the request — you get a duplicate. The fix is not guesswork: the client generates a unique identifier per operation and the server treats it as a key. The second time that identifier arrives, the server returns the original result instead of creating another record.

Without it, an intermittent network produces duplicate receipts, repeated charges and phantom orders.

Conflicts: decide before they happen

Two devices edit the same record offline. When both sync, someone wins. "Last write wins" is a valid decision when it is explicit; it stops being one when it is accidental.

For most business cases the best approach is to avoid the conflict by design: operations that add rather than overwrite, and fields with a single clear owner.

What to test

None of these guarantees are verified on good connectivity. Test in airplane mode mid-write, with the app killed during sync, and with a skewed device clock. That is where the duplicates show up.

Back to blog

Related articles

Performance6 min read

Core Web Vitals in Next.js: what to measure and what to ignore

LCP, CLS and INP explained without the hype, plus the three causes behind most production failures.

Read article
Architecture7 min read

Multi-tenant from day one: the decisions you cannot undo

Data isolation, foreign keys and migrations: why bolting on multi-tenancy later costs ten times more.

Read article