Skip to content
QSWEQB
hardDesignScenarioMidSeniorStaff

A driver spends part of the day with no mobile signal. How do you design the app so the round still works?

Treat the device as a first-class writer rather than a cache: it generates its own identifiers, records two timestamps per event, and queues durably until the network returns. Every submission carries an idempotency key because retries are certain, and genuine contradictions go to a human.

4 min readUpdated 2026-07-26

What the interviewer is scoring

  • Does the candidate treat offline as the normal case rather than an error path
  • Whether client-generated identifiers are proposed so records exist before the server knows
  • That idempotency keys are attached because retries are certain rather than possible
  • Whether device time is captured but treated as untrusted
  • Does the candidate route genuine conflicts to a human rather than resolving by timestamp

Answer

Offline is the normal case

A driver loses signal in an underground loading bay, behind steel racking, in a rural lane, in a lift. This is not an outage to be handled gracefully — it is a routine condition covering a meaningful share of the working day, and the work cannot pause until connectivity returns.

That reframes the architecture. The device is not a thin client that displays server state; it is a participant that records facts, holds them, and reconciles later. Everything below follows from taking that seriously rather than bolting a retry queue onto a request-response app.

The consequence people find uncomfortable is that the device is briefly the only place a fact exists. A delivery genuinely happened at 11:41; the server learns about it at 14:55. Between those two moments the truth is on a phone in a van, and the system has to be designed as if that is acceptable, because it is unavoidable.

Identifiers come from the device

If the server allocates identifiers, nothing can be recorded without a round trip, which defeats the purpose. So the device generates them — a UUID, or a composite of device id and a local sequence — and the server accepts them.

{
  "event_id":   "9f2c8b14-...",        // generated on the device
  "parcel_id":  "P-88213",
  "type":       "delivery_attempted",
  "outcome":    "no_answer",
  "occurred_at":"2026-07-27T11:41:02Z", // device clock, treated as suspect
  "recorded_at":"2026-07-27T14:55:31Z", // server, on receipt
  "device_id":  "VAN-742",
  "sequence":   318                     // monotonic per device
}

Two timestamps rather than one, and the distinction is load-bearing. Event time orders what happened; record time orders what you knew. A consumer that sorts by arrival concludes this parcel was attempted after it was delivered, and notifies the customer wrongly — which is the classic bug in this domain.

The per-device sequence is worth having alongside the identifier because it lets the server detect a gap: events 316, 317 and 319 arrived, so 318 is missing rather than merely late, and that is a different problem.

Every submission is idempotent, because retries are certain

Not likely — certain. A queued batch is sent, the network drops before the response, and the device cannot tell whether the server processed it. The only safe behaviour is to send again, so the server must recognise a repeat.

The event_id is the idempotency key, enforced by a uniqueness constraint at the point of persistence rather than by a check in application memory. A deduplication cache that a pod restart clears is deduplication that fails at exactly the moment a deployment coincides with a sync.

The related design choice is that events are append-only facts rather than updates to a parcel row. "Delivery attempted at 11:41" can be replayed harmlessly; "set parcel status to attempted" cannot, because by the time it is replayed the parcel may be delivered and the replay would move it backwards.

Trust the device's clock as little as you can

Phone clocks are wrong. They drift, they are adjusted manually, they jump when a timezone changes, and a driver who wants a delivery to look on time has an incentive.

So capture occurred_at because it is the only record of when the thing happened, and treat it as an assertion rather than a fact. Practically: record the device's clock offset against the server at each sync so it can be corrected afterwards; validate that event time is not in the future and not implausibly old; and where the time is legally or commercially significant — a proof of delivery underpinning a payment — prefer a server-side corroborating signal such as a geofence crossing.

Conflicts are a business decision

Two contradictory events for one parcel arrive — delivered by one device, returned to depot by another, or attempted twice with different outcomes. The tempting resolution is last-write-wins on event time.

That is wrong here, because the underlying question is not which record is newer but which physically happened, and the system does not know. A parcel cannot be both delivered and at a depot, so one of the events is mistaken, and mistaken events in this domain usually mean a mis-scan, a wrong parcel, or someone covering a failed drop.

The correct handling is to detect the contradiction, hold the parcel in an exception state, and route it to operations with both events visible. That is slower and it is the only answer that does not silently record a delivery that did not occur.

The unglamorous operational details

Photos and signatures are large and the network is bad. Queue the event separately from its attachment: the delivery fact syncs in kilobytes and unblocks the tracking page, the photograph uploads when there is bandwidth, and the event carries a reference that resolves later. Compress on the device and cap the resolution — nobody needs twelve megapixels of a doorstep.

Storage is durable, not in-memory. A queue in application memory loses the round when the operating system reclaims the app, which it will. Write to the device's database as part of completing the task, not after.

A dead battery is data loss and no amount of design prevents it. What you can do is bound it: sync opportunistically whenever any connectivity appears rather than on a timer, so the queue is minutes deep rather than hours.

The customer-facing page has to cope with the gap. For three hours the system does not know a parcel was delivered. Showing "out for delivery" is honest; showing a predicted time that has passed is not. This is a product decision that the offline design forces, and it is better made deliberately than discovered.

The phone is not a cache, it is a writer that is briefly the only source of truth. Give it identifiers, two timestamps, durable storage and idempotent sends, and send genuine contradictions to a person.

Likely follow-ups

  • The driver's phone dies with forty events queued. What have you lost?
  • Two contradictory delivery outcomes arrive for one parcel. Which wins?
  • How do you handle a proof-of-delivery photo on a bad connection?
  • What does the customer-facing tracking page show while events are queued?

Related questions

offline-firstmobileidempotencysyncproof-of-delivery