What Should Happen When You Click Twice?
A lost confirmation and a deliberate repeat can send identical requests. A fictional print order shows why the software needs to know which intention it is carrying.
· The Tellenze team
Mara has ordered forty posters for Saturday's concert. Or she thinks she has. The print studio's screen is still spinning, and the confirmation hasn't arrived.
This is a fictional studio, with a fictional order. Mara has checked the artwork and chosen the paper. She has one decision left: press the button again, or wait and risk discovering later that nothing was submitted.
At the other end, the job might already be in the queue. The reply may simply have been lost on its way back.
A second click has become a question about meaning. Does Mara want another attempt at the same order, or another forty posters?
The answer seems obvious while we're looking over her shoulder. Software needs a way to keep that distinction, even when the two requests contain exactly the same artwork, paper and quantity.
Two identical orders can both be wanted
Suppose Mara gets her confirmation. A few minutes later, another venue asks for the same posters. She opens the order and chooses “Order another forty”.
This time, creating a second job is correct. Preventing it because the details match would be a different kind of failure.
That makes “ignore duplicates” a surprisingly incomplete requirement. Duplicate clicks, duplicate request contents and duplicate intentions aren't interchangeable.
For the first order, the application can create an identifier before sending the request. If it needs to repeat that submission, it keeps the identifier. A deliberate second order gets a new one. The artwork can stay identical.
This is the central idea in Malcolm Featonby's account of idempotent APIs at Amazon: callers identify their requests explicitly, rather than leaving a service to infer intention from matching parameters. His example concerns cloud resources, where someone may genuinely want two identical instances.
For the studio, the design question comes before the identifier. Which interaction means “I'm still trying to place this order”? Which one means “I'd like another”?
Generating a new key on every click would preserve the number of attempts while losing the relationship between them. Reusing one key for every order with the same artwork would lose the intentional second job.
The identifier has to follow the decision the person made.
A quiet button helps, but it can't settle the order
There are good reasons to respond to the first click promptly. Show that the submission has started. Make accidental repeated clicks less likely. Keep the chosen quantity and artwork visible.
GOV.UK's button guidance discusses people submitting information twice, including on slow connections or through involuntary clicks. It recommends useful feedback and, where research shows the problem, a control that ignores an accidental second click. It also calls for attention on the server side.
That is a useful combination. The interface can help Mara understand what's happening; the service still has to recognize a repeated submission.
Mara might reload the page. A client library might retry without her clicking anything. Two attempts might arrive together. The original response might arrive after the screen has already given up waiting.
A disabled button cannot tell us what the server accepted.
If the studio hasn't established the result, “Order failed” is too confident. “We haven't received confirmation” describes what the application actually knows. An order-status view can help Mara get back to the same submission. If a supported retry is offered, it should continue that submission rather than quietly create a new one.
These are proposed choices for our illustration, not evidence that one message works best in every product. The important thing is that the action offered agrees with the uncertainty displayed.
What does trying again promise?
Engineers call an operation idempotent when repeating it has the same intended effect as performing it once. HTTP Semantics, RFC 9110 defines that property and warns against automatically retrying a non-idempotent request without grounds to know it is safe to do so.
A POST request doesn't acquire that promise just because the interface calls it Retry.
The studio needs to define what happens when a recognized submission is still being processed, when it has completed, and when its parameters have changed. Mara shouldn't have to interpret “already exists” as a possible confirmation of her own order.
Featonby's Amazon account discusses returning a semantically equivalent result and keeping the request record consistent with the work it creates. For the studio, that could mean bringing Mara back to the same job and showing its current status.
There is a boundary here. Recognizing one order request doesn't, by itself, prove that a separate printer service will print it only once. The guarantee must cover the effects it promises, including any downstream operation that can repeat. A stored key beside an order row is insufficient if the printing handoff has its own unprotected retry.
Different APIs also make different promises. Stripe's current idempotent-request documentation distinguishes API v1, which retains the initial response after execution starts, including an error response, from API v2, which can continue failed or partially failed work and return an updated response.
That difference matters. Repeating a recognized request doesn't always mean receiving an identical frozen answer. Read the contract for the actual operation.
The same order, with an edit
While waiting, Mara notices that she selected glossy paper. She wants matte.
Sending the changed order with the old identifier would ask the service to recognize the request and reinterpret it at the same time. The safer response is to expose the mismatch.
Stripe's API v1 documentation describes comparing parameters and rejecting a changed request under the same retained key. It also says validation failures and certain concurrent conflicts don't save a result because execution hasn't begun. Those details belong to that API's contract.
For our studio, a paper change needs its own supported path. First establish whether the original order exists. Then amend it if amendments are allowed, or place a new order through an explicit decision. Giving the edited submission a new key doesn't cancel the glossy job.
There is also a time limit to consider. Stripe documents that API v1 keys can be removed after at least twenty-four hours; using a pruned key starts a new request. Other services have other retention rules.
So Mara returning days later needs a dependable route to the order, not a Retry button that silently relies on an expired promise. The order's identity may need to remain useful much longer than the request's retry record.
When reviewing an action, try following these two journeys together: the confirmation is lost, and the person intentionally wants another identical result. Include a changed parameter and a return after the documented retry window. Ask what the person sees and which intention the next request carries.
In the studio, Mara eventually sees one confirmed job for forty posters. When the second venue calls, she can order another forty.
Both journeys should work. They just shouldn't become the same journey by accident.
Further reading
- Making retries safe with idempotent APIs — Malcolm Featonby, Amazon Builders' Library — caller intention, equivalent responses and the limits of a retained request record.
- Button — GOV.UK Design System — accidental repeated submissions and feedback on slow connections.
- HTTP Semantics, section 9.2.2 — RFC 9110 — the meaning of idempotency and the conditions for automatic retries.
- Idempotent requests — Stripe API Reference — concrete, version-specific response, parameter and retention behavior.