system-design

Practice Problem: Design a Coupon / Discount Service

Prompt: Design a coupon service for an e-commerce platform: create coupons (percentage/fixed, with usage limits and expiry), validate and redeem them at checkout, and prevent abuse (a “one per user” or “first 1000 uses” coupon must be honored exactly). Attempt cold for 45 minutes first.

Tests: redemption limits under concurrency (the oversell problem again), validation on a hot path, abuse prevention. Reuses ticketing / e-commerce reservation patterns.


Solution outline

1. Requirements

2. Estimation

Millions of validations/day (every checkout checks a coupon), far fewer redemptions. A viral coupon → a spike of concurrent redemptions on one coupon → contention (a hot key).

3. Core design — validate cheaply, redeem atomically

Split the two operations:

flowchart LR
    Checkout --> Validate{Valid & eligible?}
    Validate -- yes --> Redeem[Atomic consume:<br/>used < limit AND per-user check]
    Redeem -- success --> Apply[Apply discount]
    Redeem -- exhausted --> Reject

4. Deep dives

5. Trade-offs

| Decision | Chosen | Why | | — | — | — | | Validate vs redeem | Split: cached validate, atomic redeem | Fast hot-path reads; correctness at consume | | Limit enforcement | Atomic conditional increment | Honors limits exactly under concurrency | | Per-user limit | Unique constraint on (coupon,user) | DB enforces atomically; gives idempotency | | Hot coupon | Sharded counter | Spread contention on a viral code |

6. What a strong answer includes

Recognizing this is the oversell problem in disguise → atomic conditional consume, per-user uniqueness constraint, idempotency keyed on (coupon,user,order), the validate/redeem consistency split, and hot-coupon counter sharding + abuse controls. A weak answer reads the count then increments separately — races let a coupon exceed its limit.


Further reading