How do you stop double bookings when a request is retried?
Retried taps, flaky networks and two people clicking at once are normal. Idempotency keys, version-guarded writes and transactions are how a booking system survives them. With what we found in our own queue.
- An idempotency key makes a retried request safe: same key, same result, no second booking.
- A version-guarded write rejects the second of two conflicting changes instead of silently overwriting.
- A change across several rows should be one transaction, or a failure partway leaves the data half-updated.
- Test with concurrent runs, not just one user clicking once.
- Our own audit found a half-updated queue. The fix is tested and not yet in production.
Quick answer
Use an idempotency key so a retried request is recognised as the same request, and a version-guarded write so two people changing one record can't both succeed. Make any change that touches several rows one transaction. Then test with concurrent runs. A retried tap, a flaky connection and two receptionists clicking together are normal, so build for them.
The short answer
Three things cause most duplicate or corrupted bookings: the same request arriving twice, two changes to one record arriving together, and a multi-step change that fails halfway. Each has a standard guard.
Why engineers ask this
Because the happy path always works in a demo. Real systems get a patient who taps twice on a slow connection, a phone that retries after a timeout, and two staff who press a button within the same second. If your booking logic assumes one clean request at a time, it will break exactly when the clinic or shop is busiest.
What good looks like: three guards
| Failure | What happens without a guard | The guard |
|---|---|---|
| The same request arrives twice | Two bookings for one tap | Idempotency key. The client sends a unique key. A repeated key returns the first result and does nothing new. |
| Two changes hit one record together | The later one silently overwrites the earlier | Version-guarded write. The write carries the version you read. If the record has moved on, it’s rejected and the client reloads. |
| A change spans several rows and fails partway | The data is half-updated, such as a queue with a gap or a duplicate | One transaction, under a lock on the one hot record. Either everything changes or nothing does. |
On top of the guards, test them with concurrent runs. A test that fires a request once proves nothing about retries. A test that fires many at the same moment proves a lot.
Optimistic versioning fits most booking screens, since conflicts are rare and a rejected write is cheap to retry. A short lock fits the one record where conflicts are common, such as advancing a single doctor’s queue.
What we found in our own queue
A clinic-queue platform, built and tested on a demo clinic ahead of launch. One of the fixes below is tested but not yet in production. Nothing here has been proven under real clinic load.
On the clinic-queue platform we built, booking and queue advance both require an idempotency key plus a version-guarded write, so a retried tap can’t book twice.
Queue advance is where we found a real problem. A review showed that advancing a queue was a series of separate writes, so a failure partway could leave the queue torn. We moved it into one transaction under a per-doctor lock. It passed 48 assertions and two concurrency runs locally. It isn’t deployed to production yet, and we won’t call it fixed until it is.
The case that makes this concrete is an emergency walk-in. Reception inserts the patient right after the consultation in progress and everyone behind renumbers. If a doctor taps End at the same moment, two changes touch the same queue at once. That is exactly the situation the transaction and the lock exist for.
We found the half-updated queue in an audit, not in our tests. That’s the main lesson: test write paths under concurrent load earlier.
Where this doesn’t apply
A single-user tool, or a screen where one person owns each record, doesn’t need this machinery. And some businesses overbook on purpose, such as airlines or restaurants with expected no-shows. In that case the double booking is a policy, so make it a visible rule, not an accident of retries. We haven’t measured how often these failures occur in practice, so we don’t quote a rate.
Questions to ask any vendor, including us
- How does a retried request avoid creating a second record?
- What happens when two people change the same record at the same time?
- Which multi-step changes run as a single transaction?
- Show me the concurrency tests, not only the single-user ones.
- What did your last audit find in the write paths, and is it fixed in production?
Next: how to test before real users arrive and one backend for the app and the web. Part of the guide Buying custom software and hiring a tech vendor.
The facts about our queue come from our clinic-platform brief and notes and are tracked in our claim ledger. The three guards are standard engineering practice and our explanation of them, not a claim about a third party. We hold no incident-rate data, and no figure on this page comes from a third party.
More by GarunaCDX
Let's talk about your build.
Tell us what you are building. A founding engineer replies within one working day with how we would approach it.