← Home
💬Real-life Scenario-based Interview QuestionsInterview QuestionsNEW
Important
Experience levelSenior · 5+ YOESenior / staff depth expected

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?

ConcurrencyE-commerceDatabaseIdempotency

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) on coupon_redemptions table.
  • 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.