Lab · Demo 03 · Last card
Last-card demo
Two buyers try to buy the last card at the same moment. Without a lock, the checkout charges them both.
A marketplace of graded cards has exactly one of each. When two buyers press Buy on the last one at the same moment, both requests can read “one left”, both pass the check, and both take payment. Each line of that code is correct, and it still sells a card twice, because the check and the decrement are separate steps that another request can slip between.
The fix is to let the database settle the race. Lock the row, or make the decrement conditional, and the second checkout waits, reads the fresh stock and sees the card is gone. SwishX, which I architected and built via Lazer, sells real graded cards, so its checkout had to be oversell-proof.
- Model
- A TypeScript state machine, two buyers, one card
- Interleaving
- Fixed, so every run is reproducible
- Scope
- Generic, not SwishX’s code
- Case study
- Read the case study →
Two buyers, one card
Press Race to send two buyers for the last card.
Buyer A
Order confirmed
Cards left
−1
Row unlocked
Buyer B
Order confirmed, but the card is gone
- A Reads the stock
SELECT stock → 1Cards left: 1 - B Reads the stock
SELECT stock → 1Cards left: 1 - A Sees one left, takes payment
1 > 0 ✓ · chargeCards left: 1 - B Also sees one left, takes payment
1 > 0 ✓ · chargeCards left: 1 - A Order confirmed
UPDATE stock = stock − 1 → 0Cards left: 0 - B Order confirmed, but the card is gone
UPDATE stock = stock − 1 → −1Cards left: −1
Oversold: two paid orders for one card, stock −1.
A generic demo of the race a marketplace checkout has to handle, not client code.
Example result
Without a lock, both buyers pay and the stock ends at −1. Both see “Order confirmed”, and one of them has paid for a card that doesn’t exist.
With a lock, the second buyer waits for the row, reads the updated stock and gets “Sold out” before any payment is taken. One order goes through, and the stock ends at 0.