A coupon can only be redeemed once per user. During a major sale, thousands of requests from the same user arrive almost simultaneously. How would you prevent duplicate redemption?
How I frame it
Business rule: one row per (user_id, coupon_id). Thousands of parallel requests from one user (retry storm, script, or multi-tab) is the same race as wallet double-spend — check-then-insert always loses.
Core solution — database-enforced uniqueness
UNIQUE(user_id, coupon_id)oncoupon_redemptionstable.- Insert-first:
INSERT INTO coupon_redemptions …— success = first redeemer; duplicate key = return existing redemption or 409. - Transactional apply: Insert redemption + attach discount to order in one DB transaction.
- Idempotency-Key on apply-coupon API so client retries don't look like new attempts with different bodies.
1000 parallel POST /apply-coupon (user U, SAVE50) Request 1: INSERT (U, SAVE50) ✓ → discount applied Request 2..1000: unique violation → 409 "already redeemed"
Walkthrough
Sale opens at noon. User U opens 20 tabs, each auto-applies SAVE50. All 20 requests hit within 200ms. First INSERT wins; order gets discount. Other 19 get unique violation → return "coupon already applied" with same order id if tied to checkout session. Finance sees one redemption row for U + SAVE50.
Edge protection
Per-user rate limit on coupon endpoint sheds abusive retry before DB. CAPTCHA on checkout if fraud pattern detected.
Weak answer
"Redis SET user:coupon:used." Without DB unique constraint, Redis eviction or race still allows double discount at two app servers.
Follow-up questions
Coupon valid for one use globally (not per user)?
UNIQUE(coupon_id) or atomic decrement on remaining_uses counter with conditional update.
User cancels order after redeem — coupon restored?
Business rule: delete redemption row on cancel (if policy allows) or mark status=REVERSED — unique constraint may need partial index on active only.