Coupon and Discount System Low-Level Design

Requirements

  • Create and manage discount coupons (percentage, fixed amount, free shipping)
  • Apply coupon to a cart: validate, compute discount, prevent reuse
  • Support: single-use codes, multi-use codes with a global limit, user-specific codes
  • Prevent race conditions: same coupon applied concurrently by many users
  • 100M users, 10K coupon redemptions/second during flash sales

Data Model

Coupon(coupon_id, code VARCHAR UNIQUE, type ENUM(PERCENT,FIXED,FREESHIP),
       value DECIMAL, min_order_amount, max_uses INT, uses_count INT,
       max_uses_per_user INT, valid_from, valid_until, status ENUM(ACTIVE,EXPIRED,DISABLED),
       applicable_product_ids[], applicable_category_ids[])

CouponRedemption(redemption_id, coupon_id, user_id, order_id,
                 discount_amount, redeemed_at)

Coupon Validation

On apply coupon (before order placement):

  1. Look up coupon by code (Redis cache: key=coupon:{code}, TTL=1min)
  2. Check status=ACTIVE and valid_from <= NOW() <= valid_until
  3. Check cart total >= min_order_amount
  4. Check uses_count < max_uses (global limit not exhausted)
  5. Check user redemption count < max_uses_per_user (query CouponRedemption)
  6. Check applicable products/categories match cart items

Return: discount amount (if PERCENT: order_total * value/100, if FIXED: min(value, order_total)).

Preventing Race Conditions on Global Limit

Problem: 10K users try to redeem the last spot of a coupon simultaneously. Without locking, uses_count could exceed max_uses.

Solution: Atomic Redis decrement. Before DB insert:

# Initialize Redis counter on coupon creation:
SET coupon_remaining:{coupon_id} {max_uses - current_uses}

# On redemption attempt:
remaining = DECR coupon_remaining:{coupon_id}
if remaining < 0:
    INCR coupon_remaining:{coupon_id}  # undo
    return CouponExhausted
# Proceed with DB insert

Redis DECR is atomic — no two requests can simultaneously see remaining >= 1 and both decrement to 0. After the Redis check, insert CouponRedemption and increment uses_count in DB. If the DB insert fails (duplicate key on user+coupon), INCR to undo the Redis decrement.

Per-User Rate Limiting

Check max_uses_per_user: SELECT COUNT(*) FROM CouponRedemption WHERE coupon_id=X AND user_id=Y. Cache result (key=user_coupon_uses:{user_id}:{coupon_id}, TTL=60s). For max_uses_per_user=1 (single-use per user): use a UNIQUE constraint on (coupon_id, user_id) in CouponRedemption. Concurrent redemptions from the same user: the second INSERT fails with a duplicate key error, safely returning AlreadyUsed.

Coupon Redemption Flow

  1. Validate coupon (all checks above)
  2. Decrement Redis remaining counter
  3. Place order with discount applied
  4. INSERT CouponRedemption record
  5. UPDATE Coupon SET uses_count = uses_count + 1 (async, for reporting)

Step 3 and 4 are in a database transaction. If the transaction fails, undo step 2 (INCR Redis counter). For steps 2-4, use a saga pattern with compensation.

Bulk Coupon Generation

For marketing campaigns: generate 1M unique single-use codes. Use a CSPRNG (cryptographically secure random) to generate 8-12 character alphanumeric codes. Check for collisions in bulk: INSERT … ON CONFLICT DO NOTHING, retry for conflicts. Store in DB with status=ACTIVE; import to Redis in batch for fast lookup. Coupon codes are case-insensitive: normalize to uppercase on input.

Key Design Decisions

  • Redis atomic DECR prevents over-redemption without DB-level locking
  • UNIQUE constraint on (coupon_id, user_id) handles concurrent per-user limit checks
  • Cache coupon details in Redis (TTL=1min) to handle high lookup volume during flash sales
  • Soft delete coupon codes (status=DISABLED) — never hard delete for audit trail

Shopify system design interviews cover coupon and discount systems. See common questions for Shopify interview: coupon and discount system design.

Amazon system design covers coupon and promotions at scale. Review design patterns for Amazon interview: coupon and promotions system design.

Stripe system design covers discount codes and payment flows. See design patterns for Stripe interview: discount and payment system design.

Scroll to Top