← Blog

Building Software That Works When the Network Does Not

ControlPoint Advisory · 8/19/2026

Building Software That Works When the Network Does Not

Intermittent connectivity is the normal operating condition for most users here. Designing for it is an engineering decision, not an optional extra.

A great deal of business software is built and tested on fast, stable connections and then deployed to people using mobile data in a building with one bar of signal. The software is not wrong exactly. It is just untested against the conditions it will actually live in.

Here is what designing for unreliable networks actually involves.

The three failure modes that matter

Slow. The request will complete, eventually. Users experience this as the app being broken, because a spinner with no end is indistinguishable from a crash.

Intermittent. The connection comes and goes mid-session. This is the dangerous one, because requests fail halfway and applications that assume success corrupt data.

Absent. No connection at all. The honest requirement is: what can the user still usefully do?

Most software handles only the first, badly.

Make the app tell the truth about its state

The cheapest improvement you can make is honest feedback.

  • Show a visible indicator when the app is offline. Users tolerate offline; they do not tolerate mystery.
  • Give every long operation a timeout and a specific message. "Still trying — poor connection" is infinitely better than a spinner at second forty.
  • Never show a success state before the server has confirmed. A record that appears saved and is not saved destroys trust the first time it happens and costs you the user permanently.

Queue writes instead of blocking on them

For data entry — attendance, scores, sales, stock counts — the correct pattern is: accept the input locally, show it as pending, and send it when the connection allows.

This requires three things:

  1. Local storage of the pending item, surviving a page reload and an app close.
  2. A visible queue so the user knows five entries are waiting and has not lost them.
  3. Idempotent submission, so a retry does not create a duplicate. Generate the record's identifier on the device, not the server, and have the server ignore an identifier it has already seen.

That third point is where most implementations quietly fail. Without it, a flaky connection produces double entries, and double entries in a payment or stock table are worse than a failed save.

Reduce what has to travel

Performance on a poor network is mostly about bytes and round trips.

Send less. Paginate lists. Do not ship four hundred records because a screen might scroll. Request only the columns you display.

Ask fewer times. Three sequential requests on a 500ms-latency connection cost a second and a half before any data arrives. Combine them.

Cache aggressively and revalidate quietly. Show the last known data instantly, then update it in the background. A slightly stale list that appears immediately beats a fresh list that appears in eight seconds — for almost every screen a business user touches.

Compress and size images properly. An uncompressed photo in a list view can be larger than the entire rest of the page. Serve the size you display.

Use the service worker for what it is good at

A service worker lets your application shell load without a network round trip and lets you serve a useful offline page instead of a browser error. Practical scope:

  • Cache the application shell — the HTML, styles, and scripts — and serve it instantly.
  • Cache the last successful response for read-only screens so they are viewable offline.
  • Provide a real offline page that says what is happening and what still works.

Do not try to cache everything. An over-eager cache serves stale data and is harder to debug than the problem it solved.

Test under the conditions you are deploying into

This is the step that gets skipped, and it is the whole point.

  • Throttle the connection in browser developer tools to a slow 3G profile and use every main screen. Not glance at — actually use.
  • Turn the network off mid-save on your three most important forms. The correct outcome is a queued item, never a lost one.
  • Test on a low-end Android device, not a flagship. Rendering cost matters as much as network cost.
  • Test on a connection that is present but failing — the hardest and most realistic case.

Write these down as a release checklist. If they are not written down, they will not be run.

What to tell users

Design the copy as carefully as the code:

  • "Saved on this device — will sync when you are back online" is reassuring and accurate.
  • "3 entries waiting to sync" gives the user something to act on.
  • "Could not save. Your entry is kept — try again" is honest.
  • "Something went wrong" is none of these things.

The business case

This work is not free, and it is worth quantifying so it does not get cut. A school where teachers cannot enter scores reliably ends up with one panicked data-entry night per term and wrong results. A field team that loses half its entries files its reports twice. The cost of the failure recurs every cycle; the cost of the engineering happens once.

If you are commissioning a build, put connectivity behaviour in the brief explicitly, and ask the vendor to demonstrate it. If you would like us to review how an existing system behaves on a bad connection, book a consultation.

Found this useful? Share it.