Logic Behind The Code

Coupon discount LLD

A visual low-level design walkthrough of your coupon system, showing how coupon creation, strategy selection, validation, quantity updates, and discount calculation work together.

3 Design patterns
2 Discount strategies
Atomic + Sync Quantity safety

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

01

Single Responsibility Principle

Strategy classes calculate discounts, the factory creates coupons, the service manages storage, and the coupon validates and applies itself.

02

Open/Closed Principle

To add a new discount type, create another class implementing DiscountStrategy. The existing Coupon class stays unchanged.

03

Dependency Inversion Principle

Coupon depends on the abstraction DiscountStrategy, not directly on FlatDiscount or PercentageDiscount.

04

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);
Object creation path
Client CouponFactory DiscountStrategy Coupon CouponService

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);
Runtime call path
Client CouponService Coupon DiscountStrategy Final Amount

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);
}
Why this matters
If tomorrow we add cashback, festival, or loyalty discount, we only add another strategy implementation.

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);
    }
}
Interview answer
These are concrete strategies. They follow SRP because each class handles only one discount formula.

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;
}
Design principle
This is dependency injection. Coupon depends on the interface, so it follows Dependency Inversion.

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);
    }
}
Concurrency note
AtomicInteger protects individual operations, but the full check-then-decrement decision needs synchronization.

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);
}
Factory benefit
Creation is centralized. If default expiry or quantity changes, we update the factory instead of every client.

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;
}
Service role
The service stores coupons in a ConcurrentHashMap and delegates discount application to the coupon object.

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

Client requests coupon
CouponFactory creates coupon
CouponService validates coupon
Pick DiscountStrategy
Apply discount to cart amount
Return final payable amount

UML Diagram: IS-A and HAS-A

DiscountStrategy
FlatDiscount
PercentageDiscount
Coupon
DiscountStrategy
CouponService
Coupon
CouponFactory

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.