When the same route-handled write arrives twice, from a double click or a retry, what makes it safe to receive?
answer
- assume it arrives more than once
- the response can be lost after the work
- client guards reduce odds, not risk
- a token consumed in the same transaction
- assign a value rather than a delta
basics
~20 sOnly a server-side rule makes it safe: a one-time token recorded as used, a uniqueness constraint, or an effect that assigns a value rather than a delta. Client-side guards reduce the odds, never the risk.
solid answer
~50 sA submission can arrive more than once for reasons the page cannot see: the user double-clicks, the response is lost after the work was done and the client retries, a proxy or platform retries, or the user reloads a page that was itself the result of a `POST`. `POST` is not defined as repeatable, so the handler has to provide the guarantee. The durable techniques are all server-side — mint a one-time token when the form is rendered and have the handler record it as used inside the same transaction that does the work; enforce a uniqueness constraint on what makes the record distinct; express an edit as a conditional write against an expected version; or shape the effect so repeating it changes nothing, like assigning a state rather than incrementing a counter. Redirect-after-post removes the reload source, and disabling the submit control helps the user, but neither is a guarantee.
go deeper
Remember that a submission can reach the server twice, and that stopping the button from being clicked again does not stop the second request from other sources.
Name the sources — double click, retry after a lost response, reload of a POST result, direct call — and explain why POST is not repeatable by definition, so the handler has to provide the guarantee.
Show the working techniques and where they sit: a token consumed in the same transaction as the work, a uniqueness constraint, a conditional write against an expected version, and returning the first outcome on the repeat rather than an error.
Grade writes by what repeating them costs, from a duplicate note to a duplicate payment, and set the policy for which ones carry a token. The expensive cases are the ones nobody classified.
## Why twice is the normal case A handler that is exposed at a URL will, over enough traffic, receive the same logical submission more than once. The sources are independent of each other: - **The user.** Double-clicking a submit control, or submitting again because nothing appeared to happen on a slow connection. - **The network.** The request arrived and was processed; the response was lost on the way back. Everything the client knows is identical to a request that never arrived, so any retry — by the client, a proxy, or the platform running the function — re-sends it. - **The history entry.** If the response to a `POST` was a rendered page, reloading it re-submits. This one is removed by redirecting after success, which is a good reason to do it, but it is only one of the three. - **Deliberate replay.** The address is public; nothing prevents a caller from sending the same body ten times. So the correct framing is not "prevent duplicates" but **"make receiving the same write twice have the same effect as receiving it once"** — and where that is impossible, detect and reject the second. ## What does not solve it | Guard | What it actually does | |---|---| | Disabling the control after the first click | improves the experience for that one page; irrelevant to retries or direct calls | | A flag in client memory | lost on reload, absent before hydration, absent for non-browser callers | | Rate limiting the route | narrows the window; a retry a minute later still duplicates | | Redirect after success | removes the reload source only | Each is worth having. None survives the case where the request is sent by something that is not your page. ## Server-side techniques that do work 1. **A one-time token.** When the server renders the form it mints an identifier and includes it as a hidden field. The handler records that identifier as consumed **in the same transaction** as the work. A second arrival finds it already consumed and returns the first outcome instead of repeating the write. This is the general solution: it works for creations, where no natural key exists, and it works for writes the caller composed by hand as long as they must obtain a token first. 2. **A uniqueness constraint.** Whatever makes the record distinct in the domain gets a constraint in the store. The second insert fails at the constraint rather than succeeding quietly, and the handler translates that failure into "already done". 3. **A conditional write.** For edits, submit the version the form was rendered from and apply the change only if the stored version still matches. The second arrival carries a stale version and is rejected. This also solves the unrelated problem of two people editing the same record. 4. **An effect that is naturally repeatable.** Assigning a value — status becomes `approved`, quantity becomes `3` — produces the same result however many times it lands. Deltas — increment by one, append a row — do not. Reaching for the assigning form where the domain allows it removes the problem instead of managing it. ## Where the check has to sit The common failure is a check-then-act split: read to see whether the record exists, then insert. Two requests interleaved between the read and the insert both see nothing and both insert. The guarantee has to come from something atomic — the constraint, the conditional update, or the token consumed within the same transaction as the work. A check in application code before the write is a race, not a guard. ## What the user should see on the second arrival Rejecting a duplicate with a raw failure is a poor outcome: as far as the user is concerned they submitted once. The handler should recognise the repeat and produce the **same response as the first**, typically the redirect to the result. That way a lost response, a retried request or an impatient double-click all end in the same place, and only genuinely conflicting submissions surface as an error. This is also why the token should be recorded alongside the outcome, not merely marked as spent — otherwise the second arrival knows the work was done but has nothing to answer with. ## Deciding how far to go Not every write earns this. The question is what a second execution actually costs: a duplicated note is noise, a duplicated payment or a duplicated outbound message is an incident. Grade writes by the cost of repeating them, apply a token or a constraint to the ones that matter, and be explicit that the rest rely on being naturally repeatable. Writing it down is worth more than the technique itself, because the expensive cases are usually the ones nobody classified.
- Why is checking whether the record already exists before inserting it not enough?Two requests can interleave between the read and the insert; both see nothing and both write. The guarantee has to come from an atomic step — a uniqueness constraint, a conditional update against an expected version, or a token consumed in the same transaction as the work.
- What should the second arrival return?The same outcome as the first, usually the redirect to the result. The user submitted once as far as they know. That requires storing the outcome with the consumed token, not just marking the token spent, so the repeat has something to answer with.
- Does redirecting after a successful write make it safe to repeat?No. It removes one source — reloading a page that was the result of a POST — and leaves double clicks, network retries and direct calls untouched. It is a history fix that happens to help, not a duplicate-write control.
A mail-order coupon with a serial number: copies can arrive by any route, and the vendor honours the serial exactly once no matter how many envelopes show up.
saying these in an interview costs you the question
- Says disabling the submit button prevents duplicate writes
- Relies on a flag in client memory that a reload discards
- Checks for an existing record in code and then inserts
- Assumes a lost response means the write never happened
- Treats a repeated submission as an error the user must see
- Believes redirect-after-post makes a write safe to repeat