Loyalty Program System Low-Level Design

Requirements

  • Earn points on purchases and specific actions (sign-up, referral, review)
  • Redeem points for discounts, rewards, or free items
  • Tiered membership (Bronze, Silver, Gold, Platinum) with benefits per tier
  • Points expiry: points expire if unused for 12 months
  • 100M members, 10M transactions/day, <100ms point balance lookup

Data Model

LoyaltyAccount(account_id UUID, user_id UUID, tier ENUM(BRONZE,SILVER,GOLD,PLATINUM),
               points_balance BIGINT, lifetime_points BIGINT,
               tier_qualifying_points INT,  -- points earned in current tier year
               tier_expiry_date DATE, created_at)

PointTransaction(txn_id UUID, account_id UUID, type ENUM(EARN,REDEEM,EXPIRE,ADJUST),
                 points BIGINT, reference_id UUID, reference_type VARCHAR,
                 description, expires_at DATE NULL, created_at)
-- All point changes go through transactions — never update balance directly

PointExpiry(expiry_id UUID, account_id UUID, txn_id UUID,
            points BIGINT, expires_at DATE, status ENUM(ACTIVE,EXPIRED,REDEEMED))

Earning Points

Earning rules are configurable: 1 point per $1 spent, 3x points for a specific product category, 500 bonus points for referral, etc. Rules stored in a EarnRule table and evaluated at transaction time. The earn logic:

  1. Order completed → publish OrderCompleted event to Kafka
  2. Loyalty service consumes event, evaluates earn rules
  3. Calculate points = base_earn_rate * amount + bonus_points for applicable rules
  4. INSERT PointTransaction (type=EARN, points=calculated)
  5. UPDATE LoyaltyAccount SET points_balance += points, lifetime_points += points, tier_qualifying_points += points
  6. Evaluate tier upgrade: if tier_qualifying_points >= tier threshold, upgrade tier

Redeeming Points

def redeem_points(account_id, points_to_redeem, order_id):
    BEGIN TRANSACTION
    SELECT points_balance FROM LoyaltyAccount
    WHERE account_id = :id FOR UPDATE

    if points_balance < points_to_redeem:
        ROLLBACK; raise InsufficientPoints

    # Deduct using FIFO from expiring points first (redeem oldest first)
    deduct_from_expiry_buckets(account_id, points_to_redeem)

    UPDATE LoyaltyAccount SET points_balance -= points_to_redeem
    INSERT PointTransaction (type=REDEEM, points=-points_to_redeem, reference_id=order_id)
    COMMIT
    return calculate_discount(points_to_redeem)

Redemption rate example: 100 points = $1 discount. Deduct from expiry buckets in FIFO order — use points expiring soonest first. This reduces the number of points that expire unused.

Points Expiry

Points earned in each transaction expire after 12 months of account inactivity or on a rolling 12-month basis per earn event. Implementation: PointExpiry table tracks each earn event’s expiry date. A nightly batch job runs:

SELECT account_id, SUM(points) as expiring_points
FROM PointExpiry
WHERE expires_at = CURRENT_DATE AND status = 'ACTIVE'
GROUP BY account_id
LIMIT 10000  -- process in batches

For each account: INSERT PointTransaction (type=EXPIRE, points=-expiring_points), UPDATE LoyaltyAccount balance, mark PointExpiry records as EXPIRED. Send email notification 30 days before expiry: “You have X points expiring on DATE.”

Tier Management

Tier qualification period: typically calendar year or rolling 12 months. Tier thresholds: Bronze=0, Silver=1000 QP (qualifying points), Gold=5000 QP, Platinum=10000 QP. Tier upgrade: immediate when threshold is crossed. Tier downgrade: at tier year end, re-evaluate based on prior-year QP. Member retains previous tier for a grace period (e.g., 3 months) after year end. Cache tier status in Redis for fast benefit lookups: key=loyalty_tier:{user_id}, TTL=1h.

Key Design Decisions

  • Double-entry bookkeeping via PointTransaction — never modify balance directly, full audit trail
  • SELECT FOR UPDATE on redemption — prevents concurrent over-redemption
  • PointExpiry table for FIFO redemption — use oldest points first, minimize waste
  • Async earn via Kafka — order completion never blocks on loyalty points calculation
  • Nightly batch for expiry — avoid real-time TTL complexity, predictable processing window

Shopify system design covers loyalty programs and customer rewards. See common questions for Shopify interview: loyalty program and rewards system design.

Airbnb system design covers loyalty programs and host/guest rewards. Review patterns for Airbnb interview: loyalty and rewards system design.

Lyft system design covers loyalty programs and driver/rider rewards. See design patterns for Lyft interview: loyalty program and rewards system design.

Scroll to Top