Core Entities
Coupon: (coupon_id, code, discount_type=PERCENTAGE|FIXED_AMOUNT|FREE_SHIPPING|BUY_X_GET_Y, discount_value, min_order_amount_cents, max_discount_cents, start_date, end_date, is_active, usage_limit_global, usage_limit_per_user, applicable_to=ALL|CATEGORY|PRODUCT, applicable_ids[]). CouponUsage: (usage_id, coupon_id, user_id, order_id, discount_applied_cents, used_at). DiscountRule: for automatic discounts (no code needed, e.g., “10% off all orders above $50 in July”). CouponStack: which coupons can be combined (e.g., a welcome coupon can stack with a category coupon but not with another percentage coupon).
Coupon Validation
On coupon code submission: (1) Code exists and is active: SELECT coupon WHERE code=:code AND is_active=TRUE. (2) Date validity: NOW() BETWEEN start_date AND end_date. (3) Minimum order amount: cart_total >= coupon.min_order_amount_cents. (4) Applicable to cart items: if coupon.applicable_to=CATEGORY, at least one cart item must be in applicable_ids. (5) Global usage limit: SELECT COUNT(*) FROM coupon_usage WHERE coupon_id=:id. If count >= coupon.usage_limit_global: return “Coupon has reached its maximum uses”. (6) Per-user limit: SELECT COUNT(*) FROM coupon_usage WHERE coupon_id=:id AND user_id=:user. If count >= coupon.usage_limit_per_user: return “You have already used this coupon”. Run all checks before applying. Return the first failure with a clear error message.
Concurrency: Preventing Over-Use
Race condition: two users simultaneously apply the last use of a coupon. Both pass the usage count check (count=99, limit=100) and both apply it. Result: 101 uses of a 100-use coupon. Solutions: (1) Database UPDATE with check: UPDATE coupons SET current_uses=current_uses+1 WHERE coupon_id=:id AND current_uses usage_limit_global: DECR to undo and reject. Redis is faster but requires Redis + DB consistency. (3) Pessimistic lock: SELECT FOR UPDATE on the coupon row, check and increment in the same transaction. Safest but slowest. The Redis approach is best for high-traffic promotions (e.g., flash sale coupons).
Discount Calculation
Apply discounts after validation. Discount types: PERCENTAGE: discount_cents = round(cart_total * discount_value / 100). Cap at max_discount_cents if set. FIXED_AMOUNT: discount_cents = min(discount_value, cart_total) (cannot discount more than the order). FREE_SHIPPING: set shipping_cents = 0. BUY_X_GET_Y: find qualifying items, apply discount to the Y cheapest items. Stacking rules: check CouponStack table to determine if multiple coupons are combinable. Apply in order: free shipping first (no interaction), then fixed amounts, then percentages. Always apply to the post-previous-discount price to prevent over-discounting. Store discount_applied_cents on the CouponUsage record for accounting and analytics.
Automatic Discounts
Automatic discounts apply without a code (e.g., “Summer Sale: 20% off Electronics in July”). Store as DiscountRule with the same attributes as coupons but with auto_apply=TRUE. On cart load: evaluate all active DiscountRules against the cart. Apply all matching rules. Display applied discounts to the user in the cart summary. Priority: if two rules conflict, apply the one with higher priority (priority field on DiscountRule). Stacking: automatic discounts may stack with each other or with coupons — configurable. The cart total after all automatic discounts is the base for coupon validation.
Analytics
Track coupon performance: redemption rate (used/distributed), average discount per redemption, revenue attributed to coupons (orders that used a coupon), orders per coupon, and incremental revenue (did the coupon cause a purchase or was the user going to buy anyway?). The incremental revenue question is answered with A/B tests (show coupon to 50%, not to 50%; compare conversion and revenue). Store a coupon_distributed table (coupon_id, user_id, channel=EMAIL|PUSH|REFERRAL, distributed_at) for tracking the full funnel. Dashboards: aggregate by coupon, by campaign, by time period.
Interview Tips
- The hardest part is concurrency on usage limits during flash sales. Redis INCR + DECR on limit violation is the standard high-performance solution.
- Store the discount_applied_cents at order time — do not recalculate it later. Coupon values or rules may change; the historical discount amount must be preserved for accounting.
- Stacking rules are a design complexity that separates a basic coupon system from a production one. Always mention them in interviews as a design consideration.
Asked at: Shopify Interview Guide
Asked at: Airbnb Interview Guide
Asked at: DoorDash Interview Guide
Asked at: Snap Interview Guide
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems