Design Intent
What your code is doing
Strategy Pattern
DiscountStrategy gives every discount type the same contract. The coupon can apply flat or percentage discounts without knowing the concrete class.
Factory Pattern
CouponFactory centralizes object creation. The client passes FLAT or PERCENT, and the factory attaches the right strategy.
Singleton Pattern
CouponService is one shared in-memory registry. It uses double-checked locking with volatile for safe lazy initialization.
Design Principles
How SOLID appears in this design
Single Responsibility Principle
Strategy classes calculate discounts, the factory creates coupons, the service manages storage, and the coupon validates and applies itself.
Open/Closed Principle
To add a new discount type, create another class implementing DiscountStrategy. The existing Coupon class stays unchanged.
Dependency Inversion Principle
Coupon depends on the abstraction DiscountStrategy, not directly on FlatDiscount or PercentageDiscount.
Thread Safety
ConcurrentHashMap protects coupon storage. The synchronized coupon application block keeps validation and quantity decrement atomic.
Request Flow
How creation and application move through the classes
Creation Flow
Client asks factory to build a coupon
The factory decides which strategy object should be attached to the coupon. This keeps the client clean and avoids strategy creation logic in the UI layer.
Coupon c = CouponFactory.CreateCoupon(type, code, val);
service.addCoupon(c);
Interview line: the client only gives coupon details; the factory hides concrete object construction.
Application Flow
Service finds coupon, coupon applies strategy
The service coordinates the use case, but it does not calculate the discount. The coupon validates itself, decrements quantity, then delegates to the selected strategy.
double finalAmt = service.applyCoupon(code, amt);
Interview line: service coordinates, coupon owns validation, strategy owns discount calculation.
Code Walkthrough
Explain each important snippet like an interview answer
Interface
One contract for all discounts
DiscountStrategy lets the coupon use any discount algorithm through one method.
interface DiscountStrategy {
long applyDiscount(long amount);
}
Concrete Strategies
Flat and percentage discounts
Each class owns one calculation rule, so the logic stays small and testable.
class FlatDiscount implements DiscountStrategy {
public long applyDiscount(long amount) {
return amount - flatDiscountAmount;
}
}
class PercentageDiscount implements DiscountStrategy {
public long applyDiscount(long amount) {
return amount - (amount * flatDiscountAmountPercentage / 100);
}
}
Coupon Entity
Coupon keeps state and delegates calculation
The constructor receives the strategy, which keeps coupon independent from concrete discount classes.
class Coupon {
protected String Code;
private DiscountStrategy strategy;
private LocalDate expiryDate;
private AtomicInteger quantity;
}
Validation + Quantity
The critical thread-safe block
The check and decrement happen inside one lock so two users cannot consume the same last coupon.
public long applyCoupon(long amt) {
synchronized (this) {
if (!isValidCoupon()) {
throw new RuntimeException("Invalid coupon");
}
quantity.decrementAndGet();
return strategy.applyDiscount(amt);
}
}
Factory
Choose the right strategy at creation time
The factory keeps coupon construction logic in one place and prevents the client from directly creating strategy objects.
if (type.equalsIgnoreCase("FLAT")) {
return new Coupon(Code, new FlatDiscount((int) val),
LocalDate.now().plusDays(2), 100);
}
Singleton Service
One shared coupon registry
The service uses double-checked locking with volatile so only one instance is created safely.
public static CouponService getInstance() {
if (instance == null) {
synchronized (CouponService.class) {
if (instance == null) {
instance = new CouponService();
}
}
}
return instance;
}
Run The Code
Read it here, run it in the embedded Java compiler
Java Source
This version is compiler-friendly: no package line, class name is Main, and it runs an automatic coupon demo without console input.
Embedded Compiler
Copy the Java source on the left and paste it into the compiler. It runs directly with sample coupons, so no manual input is required.
Interview Ready
Notes For Interview
How to explain the design
This Coupon Discount LLD models coupon creation and discount application separately. A coupon owns metadata like code, type, and validity, while the actual discount calculation is delegated to a `DiscountStrategy`. That keeps business rules modular and lets the system support flat, percentage, cashback, category-based, or user-specific discounts without changing the coupon entity.
Flow Diagram
UML Diagram: IS-A and HAS-A
Core classes
- DiscountStrategy: common contract for all discount calculations.
- FlatDiscount: applies a fixed amount reduction.
- PercentageDiscount: applies a percentage-based reduction.
- Coupon: stores coupon metadata and delegates calculation to the strategy.
- CouponFactory: centralizes coupon object creation.
- CouponService: coordinates validation and application of coupons.
Why these patterns are used
Strategy Pattern is used because the discount formula is the part that changes most often. A flat discount, percentage discount, cashback, category discount, bank offer, or loyalty discount all calculate the final amount differently, but the caller should not care about the exact formula. By depending on `DiscountStrategy`, the coupon system follows Open/Closed Principle: new discount behavior can be added through a new class instead of modifying existing service logic.
Factory Pattern is useful because coupon creation can become complex. In real systems, coupon creation may depend on coupon type, expiry, minimum cart value, user segment, product category, and strategy selection. A factory centralizes that construction logic so the client does not manually wire every coupon object.
Algorithm explanation
The coupon application algorithm is: receive cart amount and coupon code, validate whether the coupon is active and eligible, select the correct discount strategy, calculate discount, cap the discount if needed, and return the final payable amount. In the current demo, the calculation is simple, but the same flow scales to production rules.
For a flat discount, the algorithm subtracts a fixed value from the amount. For a percentage discount, it computes `amount * percentage / 100` and subtracts that value. In production, this should include safeguards such as not allowing the payable amount to go below zero and applying a maximum discount cap.
Strong interview answer
"I would separate coupon metadata from discount calculation. The coupon object represents the offer, while discount formulas are implemented as strategies. This keeps the design open for new coupon types without modifying the core coupon or service logic."
Production improvements
- Use money-safe representation such as paise/cents or `BigDecimal`.
- Add eligibility rules for users, products, categories, minimum cart value, and expiry.
- Add conflict policy for stacking multiple coupons.
- Persist coupon usage and enforce redemption limits.
- Add audit logs for applied and rejected coupons.
Weakness to mention
The demo is good for LLD, but a production coupon system needs richer validation, usage tracking, concurrency control on redemption count, and precise currency handling.