Two buyers bought the last item
In this controlled demo, one item is left and two people press Buy at the same moment. Both requests check the stock, both see one, and both orders go through, so the shop sells an item it doesn't have.
Demo built and tested 29 Sept 2026 · Published 29 Sept 2026 · Last reviewed 1 Oct 2026 · By Ctrl+Z Therapy (how we test)
Why did two customers buy the last item?
Because the app checks the stock and subtracts it in two separate steps. When two purchases arrive together, both checks see one item left and both orders go through. In our controlled demo, the last camera got two orders and the stock went to -1. The fix: let the database check and subtract in one step (update only if stock is above zero) and create the order only if that update changed a row.
How to tell if your app has this
No code needed.
- Ask your AI tool: "What happens if two people buy the last item at the same time? Show me where we check and change the stock." If the check and the update are separate steps, you have this race.
- Look at your orders for items with limited stock. More orders than items, or a stock count below zero, is the symptom.
- Test it: set an item's stock to 1 in a test environment, then send two purchase requests at the same moment. One should succeed and one should be rejected.
Before and after
One camera, two orders. Stock went to -1.
One order goes through. The other buyer sees sold out.
Why AI-built apps end up with this
The natural way to write a purchase is: read the stock, check it, subtract one. It works every time you test it alone, because a race needs two requests arriving together. AI tools usually write it that way unless you ask about simultaneous buyers.
How do I fix it? Paste this into your AI tool
My app checks the stock first and subtracts it later, so two people buying the last item at the same time can both succeed. Change the purchase so the database does the check and the subtraction in one step, for example UPDATE items SET stock = stock - 1 WHERE id = ? AND stock > 0, and only create the order if that update changed exactly one row. If it changed zero rows, show a sold-out message and don't create an order. Do the same for anything else that is limited, like seats, coupons or bookings. Then test it by sending two purchase requests for the last item at the same time: one should succeed and one should be rejected.Works with Lovable, Bolt, Cursor, Replit and similar tools. Then run the check-up below on your own app.
How we checked the fix
| Same request | Before | After |
|---|---|---|
| Two buyers, 1 in stock, buy at the same time | 2 orders, stock -1 | 1 order, stock 0 |
| The second buyer | Order confirmed | Sold out, no order created |
Results from our demo test, run against the same demo before and after the fix.
Common mistakes when fixing this
- This only shows up when two requests land together, so it passes every test you run alone.
- Payments need their own plan: reserve the item first, or refund when the reservation fails.
Questions people ask
Does this only affect shops?
No. Anything limited has the same problem: event seats, booking slots, coupon uses, free trial credits or a waitlist spot.
Is a transaction enough?
Not always. A transaction that reads the stock and then writes it can still let two buyers through, depending on the database's isolation level. A conditional update (WHERE stock > 0) or locking the row makes the check and the change one step.
What about the payment?
Plan it separately. Common options are to reserve the item before charging, or to refund automatically when the reservation fails.
For developers: the cause and the fix in code
− if stock > 0: − stock = stock - 1 + UPDATE items SET stock = stock - 1 + WHERE id = ? AND stock > 0 -- 0 rows = sold out
Simplified. Your stack will look different; the principle is the same.