LLD: Design a loyalty program system for Amazon Fresh shoppers, that rewards customers for their shopping behaviour, manages point allocation and handles tier based benefits through a points wallet.
The design handles point allocation based on shopping behavior, manages tier-based benefits (Silver/Gold/Platinum), and maintains a points wallet with redemption support.
The design focuses on:
Point allocation based on shopping behavior
Tier-based benefits (Silver / Gold / Platinum)
Points wallet with redemption
Thread-safe operations (realistic production concern)
1. Low-Level Design (LLD)
Core Entities
Customer
PointsWallet
Tier
LoyaltyService
Order
Tier Rules (Example)
| Tier | Min Points | Earn Rate | Benefits |
| -------- | ---------- | --------- | ------------------------ |
| SILVER | 0 | 1x | Basic |
| GOLD | 5,000 | 1.25x | Free delivery |
| PLATINUM | 20,000 | 1.5x | Free delivery + cashback |
2. Class Design Overview
Customer
└── PointsWallet
LoyaltyService
Tier (enum)
Order
3. Executable Java Code
✅ Fully runnable with main()
✅ Thread-safe wallet
✅ Tier auto-upgrade
✅ Redeem points
Tier.java
enum Tier {
SILVER(0, 1.0),
GOLD(5000, 1.25),
PLATINUM(20000, 1.5);
private final int minPoints;
private final double multiplier;
Tier(int minPoints, double multiplier) {
this.minPoints = minPoints;
this.multiplier = multiplier;
}
public int getMinPoints() {
return minPoints;
}
public double getMultiplier() {
return multiplier;
}
public static Tier fromPoints(int points) {
if (points >= PLATINUM.minPoints) return PLATINUM;
if (points >= GOLD.minPoints) return GOLD;
return SILVER;
}
}
PointsWallet.java
class PointsWallet {
private int balance;
public synchronized void addPoints(int points) {
balance += points;
}
public synchronized void redeemPoints(int points) {
if (balance < points) {
throw new IllegalArgumentException("Insufficient points");
}
balance -= points;
}
public synchronized int getBalance() {
return balance;
}
}
Customer.java
class Customer {
private final String customerId;
private Tier tier;
private final PointsWallet wallet;
public Customer(String customerId) {
this.customerId = customerId;
this.wallet = new PointsWallet();
this.tier = Tier.SILVER;
}
public String getCustomerId() {
return customerId;
}
public Tier getTier() {
return tier;
}
public PointsWallet getWallet() {
return wallet;
}
public void evaluateTier() {
this.tier = Tier.fromPoints(wallet.getBalance());
}
}
Order.java
class Order {
private final String orderId;
private final double orderAmount;
public Order(String orderId, double orderAmount) {
this.orderId = orderId;
this.orderAmount = orderAmount;
}
public double getOrderAmount() {
return orderAmount;
}
}
LoyaltyService.java
class LoyaltyService {
// Rule: 1 point per ₹10 spent
private static final int BASE_RATE = 10;
public void processOrder(Customer customer, Order order) {
int basePoints = (int) (order.getOrderAmount() / BASE_RATE);
int finalPoints = (int) (basePoints * customer.getTier().getMultiplier());
customer.getWallet().addPoints(finalPoints);
customer.evaluateTier();
System.out.println("Order processed. Points credited: " + finalPoints);
}
public void redeem(Customer customer, int points) {
customer.getWallet().redeemPoints(points);
customer.evaluateTier();
System.out.println("Redeemed " + points + " points");
}
}
Main.java (Execution Entry Point)
public class Main {
public static void main(String[] args) {
LoyaltyService loyaltyService = new LoyaltyService();
Customer customer = new Customer("CUST-101");
Order order1 = new Order("ORD-1", 12000);
Order order2 = new Order("ORD-2", 18000);
loyaltyService.processOrder(customer, order1);
loyaltyService.processOrder(customer, order2);
System.out.println("Wallet Balance: " + customer.getWallet().getBalance());
System.out.println("Current Tier: " + customer.getTier());
loyaltyService.redeem(customer, 2000);
System.out.println("Wallet Balance After Redemption: " +
customer.getWallet().getBalance());
System.out.println("Final Tier: " + customer.getTier());
}
}
4. Key Design Decisions (Interview Ready)
Thread safety:
synchronizedwallet operations prevent double-spendTier recalculation: Event-driven after point change
Extensible: New tiers or earning rules can be added easily
Separation of concerns: Wallet ≠ Loyalty logic
5. Possible Follow-ups (Let me know if you want)
Multithreaded order simulation
DB schema (points ledger vs balance)
Expiry-based points
Promotions (2x points on weekends)
Distributed version (Redis + Kafka)

