100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

How to Design a Coupon System

Learn how to design a coupon system with atomic redemption checks, live eligibility validation, and protection against viral spikes.

mediumQ77 of 224 in System Design Est. time: 5 minsLast updated:
Open Code Lab

Expected Interview Answer

A coupon system stores codes and their rules (discount type, eligibility, usage caps) separately from redemption records, validates each redemption against remaining global and per-user limits with an atomic check to prevent race-condition overuse, and applies discounts at checkout time against live cart and pricing data.

Coupon definitions (code, discount type — percentage, fixed amount, or free shipping — eligibility rules, start/end dates, total redemption cap, per-user cap) live in one table or service, while each successful redemption is recorded as a separate row referencing the coupon and user, which lets the system answer “has this user already used this code” and “how many total redemptions remain” without recomputing from scratch. The redemption check itself must be atomic — a conditional decrement of a remaining-uses counter or a unique constraint on (coupon_id, user_id) — so that two simultaneous requests for the last remaining use of a popular code cannot both succeed. Stacking rules (can this coupon combine with another, or with an existing sale price) and eligibility rules (minimum cart value, specific categories, first-time users only) are evaluated at checkout against the live cart, never against a cached snapshot, so a coupon cannot be exploited by manipulating cart contents after validation. High-traffic codes (e.g., an influencer-shared code) need the same rate-limiting and caching considerations as a flash sale, since validation reads can spike sharply.

  • Separating coupon definitions from redemption records keeps usage auditable and queryable
  • Atomic redemption checks prevent a popular code from being over-redeemed under concurrency
  • Live checkout-time validation prevents cart manipulation from bypassing eligibility rules
  • Caching hot coupon lookups protects the database during viral code sharing

AI Mentor Explanation

A coupon system is like a limited allocation of complimentary match-day passes distributed by a cricket board, where each pass code has a fixed print run and rules about which stands it is valid for. Gate staff check a shared ledger before letting a pass in, so the same code cannot be used twice by different fans at different gates, mirroring an atomic redemption check. The rules for which pass works at which entrance are checked at the actual turnstile, not from an old printed list, mirroring live checkout-time eligibility validation. When a star player’s fan club shares one pass code widely, extra staff are added at that gate to handle the surge, similar to caching and rate-limiting a viral coupon code.

Step-by-Step Explanation

  1. Step 1

    Separate definitions from redemptions

    Store coupon rules (discount, eligibility, caps, dates) in one place and each successful redemption as its own auditable record.

  2. Step 2

    Validate eligibility at checkout

    Check minimum cart value, category restrictions, and stacking rules against the live cart, never a cached snapshot.

  3. Step 3

    Enforce limits atomically

    Use an atomic decrement of remaining uses, or a unique constraint on (coupon, user), so concurrent requests cannot over-redeem the last use.

  4. Step 4

    Protect against viral spikes

    Cache hot coupon lookups and rate-limit validation traffic when a code is shared widely, similar to flash-sale protections.

What Interviewer Expects

  • Separates coupon definition data from redemption/usage records
  • Proposes an atomic check to prevent overusing a code under concurrency
  • Validates eligibility and stacking rules against live cart state at checkout
  • Considers caching/rate-limiting for a suddenly viral coupon code

Common Mistakes

  • Checking remaining uses with a non-atomic read-then-write, allowing over-redemption
  • Validating a coupon once at add-to-cart time instead of re-checking at checkout
  • Not enforcing a per-user redemption limit, letting one user reuse a single-use code
  • Ignoring the traffic spike risk when a code is shared publicly and goes viral

Best Answer (HR Friendly)

A coupon system needs to track how many times a discount code has been used and by whom, and make sure that check happens safely even if lots of people try to use the same code at the same moment. The discount rules — like minimum purchase amount or which products qualify — get checked right at checkout using the real cart, not an old cached version, so the coupon can't be exploited.

Code Example

Atomic coupon redemption with per-user and global caps
def redeem_coupon(code, user_id, cart):
    coupon = coupon_repo.get_active(code)
    if not coupon:
        raise InvalidCouponError(code)

    if cart.subtotal < coupon.min_cart_value:
        raise NotEligibleError("cart below minimum value")

    if coupon.first_time_only and not user_repo.is_first_purchase(user_id):
        raise NotEligibleError("first-time customers only")

    # Atomic: unique constraint on (coupon_id, user_id) blocks double redemption
    # and a conditional UPDATE guards the global cap in one round trip.
    updated = db.execute(
        """
        UPDATE coupons
        SET remaining_uses = remaining_uses - 1
        WHERE id = %s AND remaining_uses > 0
        """,
        [coupon.id],
    )
    if updated.rowcount == 0:
        raise CouponExhaustedError(code)

    try:
        db.execute(
            "INSERT INTO redemptions (coupon_id, user_id) VALUES (%s, %s)",
            [coupon.id, user_id],
        )
    except UniqueViolation:
        db.execute(
            "UPDATE coupons SET remaining_uses = remaining_uses + 1 WHERE id = %s",
            [coupon.id],
        )
        raise AlreadyRedeemedError(code)

    return apply_discount(cart, coupon)

Follow-up Questions

  • How would you support coupon stacking rules (e.g., cannot combine with sale prices)?
  • How would you cache coupon validation for a code shared virally without risking stale eligibility checks?
  • How would you handle a coupon that expires mid-checkout while the user is paying?
  • How would you detect and prevent coupon abuse via bots creating many accounts?

MCQ Practice

1. Why should coupon eligibility be checked again at checkout rather than only when the code is first applied?

Cart contents or eligibility can change between when a coupon is applied and when checkout completes, so revalidating prevents bypassing rules.

2. What prevents two simultaneous requests from both redeeming the last remaining use of a coupon?

An atomic conditional decrement or a unique constraint ensures only one of the concurrent requests can succeed in claiming the last use.

3. Why separate coupon definitions from redemption records?

Storing each redemption separately creates an auditable trail and lets the system efficiently check per-user and global usage limits.

Flash Cards

Why separate coupon rules from redemption records?For auditability and to efficiently check per-user/global usage limits.

How to prevent over-redemption of a popular code?Use an atomic conditional decrement or a unique constraint on (coupon, user).

When should eligibility rules be checked?At checkout, against the live cart, not a cached snapshot from when the code was applied.

How to handle a viral coupon code?Cache hot lookups and rate-limit validation traffic, similar to flash-sale protections.

1 / 4

Continue Learning