Design a Loyalty Points System
viaLeetCode
Problem Low-level design of a loyalty points system: users earn points from qualifying events and redeem (exhaust) them, with balances kept consistent and points that can expire.
Requirements
- earn(userId, amount, source, expiryPolicy) on qualifying actions (bookings, promos); redeem(userId, amount) failing on insufficient balance; getBalance(userId); expiry of unspent points; full auditable history.
Core design
- LEDGER over mutable balance: PointTransaction(id, userId, type EARN/REDEEM/EXPIRE/ADJUST, amount, earnedAt, expiresAt, remaining) — append-only; balance = Σ active earn.remaining. Cached Balance object maintained transactionally for O(1) reads (and reconciled from the ledger).
- Redemption consumes earn lots FIFO-by-expiry (spend soonest-expiring first — the customer-friendly and standard policy): walk open lots decrementing
remaining— this is why lots carry remaining, not just amount. - Expiry: scheduled job (or lazy-on-read) emitting EXPIRE transactions for lots past expiresAt with remaining > 0.
- Classes: User, PointsAccount, PointTransaction/EarnLot, RedemptionService + EarningService behind a PointsService facade; EarningRule strategy (per source/promo multipliers).
Discussion points
- Why append-only + derived balance beats update-in-place: audit, concurrent-correction safety, replayability; idempotency keys per event so retries don't double-credit.
- Concurrency on redeem: two simultaneous redemptions — transactional check-and-decrement (row lock on account or optimistic version).
- Extensions: tiers, points holds (pending bookings), partial refunds re-crediting expired lots — where each fits the model.
asked …