Network uncertainty makes retry inevitable
Aliases: at-least-once user · tap again · timeout retry
What it is
A request goes out and the reply is late: it may have succeeded, may still be in flight, may have dropped. People cannot tell the three apart, so they do it again. That is not a miss-tap. It is a rational policy under network uncertainty — at-least-once user behaviour. A product that books as if each person taps once will be scrambled by the people who tap twice.
This leaf is why retry will happen. Disabling the button first, and how the server recognises two arrivals as one, are later means, not this cause.
Why it happens
One remote write is one act in the user’s eyes and two beats in the system: “sent” and “know the result”. The blank between them is an uncertainty window. If the screen is still the pre-completion look, the model is “it did not take”; if a spinner has no conclusion, the model is “maybe it took, maybe not”. Both mark a second act as reasonable: the first to make up, the second to confirm. People will not protect a server at the cost of “the money may have gone or may not”.
Timeouts, error pages, a killed app, a system “no network” then restore, all resend the same intent. Retry is not only the button: pull-to-refresh, Enter, the OS “restore this page”, a payments SDK charging again. So “users will retry” is a statistical fact, not a prediction about one control. The design object is a repeated intent, not a repeated click.
Where it stops holding
Read-only requests retried usually carry no business harm; pressure here is low. When local instantaneous feedback already closed causation (a switch that flips without the network), people retry that intent less — but if the server result never arrives they will still flip the setting again. Professional trading and ticket scalping use retry as a tool, at rates far above ordinary users; a lab “tapped twice” is not an upper bound. A definite failure (“insufficient funds”, “no permission”) closes the uncertainty; people will not retry the same parameters, they will change them or stop. That second arrival is a new intent, not this leaf’s retry.
Applying it
- Treat timeout, process-kill, and coming back from background as sources of “the same intent may arrive again”, not only double-click.
- In the uncertainty window do not offer a blank that can be read as “nothing happened”. “Still submitting, do not pay twice” is not enough — assume they will still try.
- Fold multiple arrivals into one with an identity for the intent, rather than educating “please do not tap again”.
- How to check: tap submit, then cut the network to loss, wait out the timeout. Count how many people tap again, pull, or re-enter the page. Non-zero is inevitable. Then replay the same request once after timeout: two orders, two mails, two charges means the product treated uncertainty as once-only.