Two requests. One idempotency key. A wallet that loses €20 instead of €10. Step through the race condition yourself—no IntelliJ, Java, login or internet needed.
Production-style incident
A duplicate debit
A fictional wallet service receives two simultaneous identical payment requests using payment-123 as the idempotency key. The wallet starts at €100. It should deduct €10 once; under one interleaving, it deducts twice.
Mission
Find the non-atomic sequence and explain why a thread-safe map alone does not fix it.
Simplified Java teaching excerpt
Where is the race window?
PaymentReceipt previous = receipts.get(key);
if (previous != null) return previous;
// Both threads may pass the check before either writes.
wallet.debit(1000); // cents
PaymentReceipt receipt = createReceipt(key, 1000);
receipts.put(key, receipt);
return receipt;
The get → debit → put sequence is not atomic. Even with ConcurrentHashMap, independent operations can interleave.
Interactive state machine
Watch both requests interleave
Use Next Step or Play. This deterministic simulation does not run Java or contact a payment service.
Wallet balance
€100
Applied debits
0
Receipt stored
No
Knowledge check
Does ConcurrentHashMap alone solve the bug?
Select the most accurate answer:
Continue learning
Four investigations. One complete offline workbook.
The full product includes guided fixes, architecture trade-offs, deeper simulations, assessments and all four labs: concurrency, JPA performance, Spring transactions and transactional outbox.
Backend Engineering Lab — Volume 1 · $19.99
Want the complete four-lab experience? The complete four-lab workbook is available through our Payhip store. Explore Volume 1 there.