Empower growth and innovation with the latest Mobile App insights

Why Does Double-Tapping the Order Button in an App Create an Extra Order — and Why Doesn't It Reproduce in Testing but Happens to Users After Launch?

Sep 24, 2026 Read: 77

To start with the conclusion: when one tap in an app creates two orders, it is usually not one person's fault, but the combined result of frontend debouncing, network retries, and server-side deduplication. Greying out the frontend only blocks accidental taps; what truly provides a fallback is a server-side idempotency key plus a database unique constraint. A common approach in 2026 is to do both ends at once: add a submit lock and a one-time token on the frontend, deduplicate by business semantics on the server, and keep duplicate requests as searchable, reconcilable records. This is also what this article answers: why it did not reproduce in testing, yet users encountered it after launch?

Why duplicate submission is harder to handle than an API error

Duplicate submission means the user acted only once, but the system wrote two or more business records. It differs from an API error: an error will be seen by someone, while duplicate submission often shows a normal UI and no abnormal logs, only surfacing later during shipping, reconciliation, or coupon issuance.

Based on 2026 mobile delivery experience, duplicate requests mainly come from three sources: users tapping quickly or accidentally; client-side timeout retries, or resending after returning from the background; and gateway or network library retry policies that amplify one call into multiple. Only one of the three counts as a user problem; the other two are link design problems.

  • High impact: inventory, coupons, points, balances, and refunds can all directly produce asset discrepancies, and customer complaints often come from finance reconciliation rather than testing.
  • High troubleshooting cost: without a unique constraint, both records look legal, and locating the issue depends on packet capture and request ID comparison.
  • More expensive to add after launch: adding idempotency usually requires cleaning up historical dirty data and compensating users, with workload typically half to one times what was reserved during development.

First determine which layer the problem is in: a four-step investigation

When you encounter duplicate data, do not change code first; locate the issue first. A faster way to determine responsibility is to compare the request IDs and idempotency keys of the two requests: if the same key wrote two records, it is basically on the server side; if the two keys differ, it is mostly in the frontend or retry chain.

  1. Reproduce with packet capture: confirm how many requests the client actually sent. More than two in the same second means the frontend submit lock basically did not take effect.
  2. Compare request identifiers: check whether the request ID and idempotency key in the request headers are consistent. If the client generates a new random number each time, idempotency is effectively useless.
  3. Check retry configuration: automatic retries in gateways, network libraries, and SDKs are commonly 1–3 times; setting the timeout too short makes retries more frequent.
  4. Verify database constraints: whether there is a unique index on the business unique key is the key to whether the fallback holds.

The third step is easily overlooked: many cases that look like "server-side duplication" are actually automatic client retries after a timeout, and the server processed both normally, so no error appears in the logs.

A common situation in projects is: only two weeks were allocated for the ordering flow in this iteration, the frontend greyed out the button, and the API added a single "does it already exist" check before delivery and launch. As a result, on the first day of the canary release, one order deducted a coupon twice, forcing a pause in iteration to go back and add server-side idempotency. Based on the experience range, the workload for this kind of retrofit is typically 30% to 50% of the original ordering module development time, adding three to five days to the schedule, plus separate user compensation.

A practical three-layer anti-duplication framework

Anti-duplication should not rest on only one layer. Based on 2026 project delivery habits, it can be divided into "three-layer interception": the interaction layer controls the operation method, the transport layer controls repeated sending, and the data layer controls the final write. The three layers are backups for each other, not an either-or choice.

  1. Layer one, interaction layer: the submit button enters loading and is locked, combined with debouncing and throttling. Low cost, but the state resets after page rebuild or returning from the background, so it cannot block resending.
  2. Layer two, transport layer: the server issues a one-time token that becomes invalid immediately after submission; requests uniformly carry a request ID, and automatic retries are only enabled for query-type APIs. Note that the token must be short-lived and must not be stored locally for long-term reuse.
  3. Layer three, data layer: idempotency key plus unique index. The second request with the same idempotency key should return the same result as the first, not an error, and not another record.

The pass criteria can be quantified: tapping the button multiple times produces only one record; retrying after a weak-network timeout yields a final result consistent with a single submission; replaying the same request twice returns success on the second attempt without adding data; and duplicate requests in the logs can be associated with the same idempotency key.

How to choose among three common approaches

The three approaches are not substitutes, but are used in combination according to business risk. Based on the experience range, their investments are roughly as follows, and the starting point for judgment is simple: will this operation write data, and will it move money?

  • Frontend debouncing plus button greying out: investment is typically under half a day; it covers accidental taps and fast tapping, but cannot block network retries, request replay, or simultaneous multi-device operations.
  • One-time token: investment is typically 1–2 days; it covers duplicate client sending, but has limited coverage for script replay and cross-device concurrency.
  • Idempotency key plus unique index: investment is typically 1–3 days (depending on the number of business tables involved); it covers retries, replay, and concurrent writes, at the cost of designing the key generation rules and retention period.

If payment callbacks are involved, status checks must also be added in callback handling: first read the order's current status, then decide whether to change the balance. You cannot do a conditional update based only on amount and order number, otherwise duplicate callbacks will process the same payment twice. For actions involving funds, idempotency is a hard requirement, not an option.

Common pitfalls and acceptance criteria

Most pitfalls are not about technical difficulty, but about "assuming it has already been done." The following points are mentioned relatively often in delivery reviews.

  • Only debounce on the frontend and do no deduplication at the API layer, so switching devices or script-based order flooding directly breaks it.
  • The idempotency key includes a timestamp or page random number, making every request different, which is as good as not doing it.
  • After a timeout, showing "Submission failed, please retry" leads the user to place a second order; the correct prompt should be "Processing, please check the list later."
  • The token is stored locally for a long time or reused across pages, remaining valid on replay.
  • Only the "create" path is handled, forgetting that reverse operations such as refunds, withdrawals, and cancellations also need idempotency.

Acceptance criteria should be checked by scenario, not by tapping the button once: all four cases must be covered—rapid tapping, weak network, returning from the background, and request replay. Based on enterprise project delivery habits, this kind of check is usually placed at two points: integration testing and acceptance. If it fails, send it back for rework; do not wait until the canary period to validate with real user data.

Applicable scenarios and boundaries

The criterion can be stated more plainly: if one operation will create a record, deduct a balance, or issue a coupon, it is worth doing anti-duplication; debouncing on read-only APIs is mainly to save bandwidth, not mandatory.

  • Recommended: actions with side effects such as ordering, payment, refund, withdrawal, coupon claiming, check-in, registration, and comment posting.
  • Can skip: pure queries, static display pages, internal one-off tools, and local draft saving—drafts should actually allow repeated saving.
  • Needs separate evaluation: counting scenarios that can tolerate a small amount of duplication, such as likes and view counts, can start with server-side aggregation statistics and do not need the full idempotency chain.

FAQ

The button is already greyed out, so why can duplicate orders still happen?

Greying out is only valid within the current page lifecycle. When returning from the background, rebuilding the page, or retrying over the network, the button state resets, the second request can still be sent, and the server must still deduplicate as a fallback.

Can the idempotency key be a random UUID generated by the client?

A common approach is for the server to issue a token, or to compose a key from user ID plus business identifier. A pure client-side random number changes on reinstall, data clearing, or request replay, and cannot block duplicate writes.

When the API times out, what should the frontend show?

Do not show a failure prompt that encourages retry; instead show "Processing, please check the order list later." The server judges by idempotency key and returns the original result if already processed.

Do small projects with a small user base also need this setup?

As long as the operation writes data or moves money, it is recommended. Based on 2026 delivery experience, adding idempotency to core APIs typically costs half a day to three days, which is cheaper than cleaning up dirty data after launch.


In terms of action, start with one thing: list the APIs that write data, sort them by whether funds are involved, and first add idempotency keys and unique indexes to the three entry points of payment, refund, and coupon claiming. If the project is still in development, writing it into the acceptance criteria in the requirements document is much less trouble than adding it after launch. Internal tools with a very small user base and pure display can skip it for now; once asset accounts are involved, it is not recommended to skip this step.

Interested in this topic?
10-year tech team — reference proposal within 24 hours
Obtain Proposal
Are you ready?
Then reach out to us!
+86-13370032918
Discover more services, feel free to contact us anytime.
Please fill in your requirements
What services would you like us to provide for you?
Your Budget
ct.
Our WeChat
Professional technical solutions
Phone
+86-13370032918 (Manager Jin)
The phone is busy or unavailable; feel free to add me on WeChat.
E-mail
349077570@qq.com
Submitted successfully
Thank you for your trust. We will contact you soon!
Recommended projects for you