Limited time: AI code review, hints, mock interviews, whiteboard analysis, and all Pro features are unlocked. Enroll
โฑ๏ธ 27 min read

Designing an E-commerce Cart System

Difficulty: Intermediate Patterns: Strategy, Observer, Builder Asked at: Amazon, Flipkart, Myntra, Meesho


Functional Requirements

  1. Add/remove items - add products to cart, remove by product ID
  2. Quantity updates - increase or decrease quantity of items in cart
  3. Apply coupons/discounts - percentage off, flat discount, buy-X-get-Y
  4. Price calculation with tax - subtotal, discount, tax (GST), and final total
  5. Stock validation - verify product availability before adding and at checkout
  6. Checkout - validate cart, apply pricing, generate order summary

Non-Functional Requirements

  1. Thread-safety - concurrent add/remove operations must not corrupt cart state
  2. Extensibility - new discount types or tax rules require minimal code changes
  3. Accurate pricing - rounding handled correctly; no floating-point drift on totals

Core Entities

Entity Description
Product ID, name, price, category
CartItem Product reference + quantity in cart
Cart Collection of CartItems, holds applied coupons
Coupon Discount rule: percentage, flat, or buy-X-get-Y
DiscountStrategy Interface for computing discount amount
TaxCalculator Computes tax based on product category and region
StockService Validates product availability
Order Final checkout result with itemized breakdown
OrderBuilder Builds an Order step by step

Class Diagram

classDiagram
    class Product {
        -String id
        -String name
        -double price
        -String category
        -int stockQuantity
    }

    class CartItem {
        -Product product
        -int quantity
        +getSubtotal() double
        +updateQuantity(int qty)
    }

    class Coupon {
        -String code
        -DiscountStrategy strategy
        -double minCartValue
        -boolean active
        +apply(Cart) double
        +isApplicable(Cart) boolean
    }

    class DiscountStrategy {
        <<interface>>
        +calculateDiscount(Cart cart) double
        +getDescription() String
    }

    class PercentageDiscount {
        -double percentage
        +calculateDiscount(Cart) double
    }

    class FlatDiscount {
        -double amount
        +calculateDiscount(Cart) double
    }

    class BuyXGetYDiscount {
        -String productId
        -int buyCount
        -int freeCount
        +calculateDiscount(Cart) double
    }

    class StockObserver {
        <<interface>>
        +onStockUpdated(Product product, int newQty)
        +onOutOfStock(Product product)
    }

    class Cart {
        -Map~String, CartItem~ items
        -List~Coupon~ appliedCoupons
        -StockService stockService
        +addItem(Product, int qty)
        +removeItem(String productId)
        +updateQuantity(String productId, int qty)
        +applyCoupon(Coupon coupon)
        +getSubtotal() double
        +getTotal() double
        +checkout() Order
    }

    class TaxCalculator {
        +calculateTax(CartItem item) double
    }

    class Order {
        -String orderId
        -List~CartItem~ items
        -double subtotal
        -double discount
        -double tax
        -double total
    }

    Cart --> CartItem
    CartItem --> Product
    Cart --> Coupon
    Coupon --> DiscountStrategy
    DiscountStrategy <|.. PercentageDiscount
    DiscountStrategy <|.. FlatDiscount
    DiscountStrategy <|.. BuyXGetYDiscount
    Cart --> TaxCalculator

Design Patterns

Pattern Where Why
Strategy DiscountStrategy with Percentage/Flat/BuyXGetY implementations New discount types = one new class, zero changes to Cart
Observer StockObserver notified when stock changes Cart reacts to out-of-stock events without polling
Builder OrderBuilder constructs Order step by step Complex order object with many optional fields
Composition Cart HAS CartItems, each HAS Product Clean ownership hierarchy

Data Structures

Component Structure Why
Cart items ConcurrentHashMap<String, CartItem> O(1) add/remove/update by product ID, thread-safe
Applied coupons ArrayList<Coupon> Ordered application matters (stacking rules)
Product catalog HashMap<String, Product> O(1) product lookup
Stock levels ConcurrentHashMap<String, AtomicInteger> Thread-safe stock decrement

How It All Fits Together

Hereโ€™s what happens during a complete shopping flow:

  1. Customer browses catalog, selects a product
  2. Client calls cart.addItem(product, quantity)
  3. Cart checks StockService.isAvailable(productId, quantity)
  4. If stock sufficient โ†’ CartItem created/updated, stock reserved
  5. Customer applies coupon โ†’ cart.applyCoupon(coupon)
  6. Coupon checks minCartValue and applies its DiscountStrategy
  7. Customer calls cart.checkout()
  8. Checkout validates all items still in stock
  9. TaxCalculator computes tax per item based on category
  10. OrderBuilder assembles final Order (subtotal - discount + tax = total)
  11. Stock permanently decremented, cart cleared, Order returned

Complete Code

Product and CartItem

Product is an immutable catalog entity. CartItem tracks how many of a product are in the cart and computes line-item subtotals.

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;

// โ”€โ”€โ”€ Product โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Product {
    private final String id;
    private final String name;
    private final double price;
    private final String category;

    public Product(String id, String name, double price, String category) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.category = category;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public double getPrice() { return price; }
    public String getCategory() { return category; }

    @Override
    public String toString() {
        return name + " (โ‚น" + String.format("%.2f", price) + ")";
    }
}

// โ”€โ”€โ”€ CartItem โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class CartItem {
    private final Product product;
    private int quantity;

    public CartItem(Product product, int quantity) {
        this.product = product;
        this.quantity = quantity;
    }

    public Product getProduct() { return product; }
    public int getQuantity() { return quantity; }

    public void setQuantity(int quantity) {
        if (quantity < 0) throw new IllegalArgumentException("Quantity cannot be negative");
        this.quantity = quantity;
    }

    public double getSubtotal() {
        return product.getPrice() * quantity;
    }

    @Override
    public String toString() {
        return product.getName() + " x" + quantity + " = โ‚น" + String.format("%.2f", getSubtotal());
    }
}

// โ”€โ”€โ”€ Discount Strategy Interface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
interface DiscountStrategy {
    double calculateDiscount(List<CartItem> items);
    String getDescription();
}

// โ”€โ”€โ”€ Percentage Discount โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class PercentageDiscount implements DiscountStrategy {
    private final double percentage;

    public PercentageDiscount(double percentage) {
        if (percentage < 0 || percentage > 100)
            throw new IllegalArgumentException("Percentage must be 0-100");
        this.percentage = percentage;
    }

    @Override
    public double calculateDiscount(List<CartItem> items) {
        double subtotal = items.stream().mapToDouble(CartItem::getSubtotal).sum();
        return subtotal * percentage / 100.0;
    }

    @Override
    public String getDescription() {
        return percentage + "% off";
    }
}

// โ”€โ”€โ”€ Flat Discount โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class FlatDiscount implements DiscountStrategy {
    private final double amount;

    public FlatDiscount(double amount) {
        this.amount = amount;
    }

    @Override
    public double calculateDiscount(List<CartItem> items) {
        double subtotal = items.stream().mapToDouble(CartItem::getSubtotal).sum();
        return Math.min(amount, subtotal); // discount cannot exceed subtotal
    }

    @Override
    public String getDescription() {
        return "โ‚น" + String.format("%.2f", amount) + " off";
    }
}

// โ”€โ”€โ”€ Buy X Get Y Free โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class BuyXGetYDiscount implements DiscountStrategy {
    private final String productId;
    private final int buyCount;
    private final int freeCount;

    public BuyXGetYDiscount(String productId, int buyCount, int freeCount) {
        this.productId = productId;
        this.buyCount = buyCount;
        this.freeCount = freeCount;
    }

    @Override
    public double calculateDiscount(List<CartItem> items) {
        for (CartItem item : items) {
            if (item.getProduct().getId().equals(productId)) {
                int qty = item.getQuantity();
                int sets = qty / (buyCount + freeCount);
                int freeItems = sets * freeCount;
                return freeItems * item.getProduct().getPrice();
            }
        }
        return 0;
    }

    @Override
    public String getDescription() {
        return "Buy " + buyCount + " Get " + freeCount + " Free";
    }
}

// โ”€โ”€โ”€ Coupon โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Coupon {
    private final String code;
    private final DiscountStrategy strategy;
    private final double minCartValue;
    private boolean active;

    public Coupon(String code, DiscountStrategy strategy, double minCartValue) {
        this.code = code;
        this.strategy = strategy;
        this.minCartValue = minCartValue;
        this.active = true;
    }

    public boolean isApplicable(double cartSubtotal) {
        return active && cartSubtotal >= minCartValue;
    }

    public double apply(List<CartItem> items) {
        return strategy.calculateDiscount(items);
    }

    public String getCode() { return code; }
    public String getDescription() { return strategy.getDescription(); }
    public void deactivate() { this.active = false; }

    @Override
    public String toString() {
        return "Coupon[" + code + ": " + strategy.getDescription() + "]";
    }
}

// โ”€โ”€โ”€ Stock Service โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class StockService {
    private final ConcurrentHashMap<String, AtomicInteger> stock;
    private final List<StockObserver> observers;

    public StockService() {
        this.stock = new ConcurrentHashMap<>();
        this.observers = new CopyOnWriteArrayList<>();
    }

    public void addObserver(StockObserver observer) {
        observers.add(observer);
    }

    public void setStock(String productId, int quantity) {
        stock.put(productId, new AtomicInteger(quantity));
    }

    public boolean isAvailable(String productId, int requiredQty) {
        AtomicInteger qty = stock.get(productId);
        return qty != null && qty.get() >= requiredQty;
    }

    public boolean reserve(String productId, int quantity) {
        AtomicInteger current = stock.get(productId);
        if (current == null) return false;

        while (true) {
            int existing = current.get();
            if (existing < quantity) return false;
            if (current.compareAndSet(existing, existing - quantity)) {
                if (current.get() == 0) {
                    observers.forEach(o -> o.onOutOfStock(productId));
                }
                return true;
            }
        }
    }

    public void release(String productId, int quantity) {
        AtomicInteger current = stock.get(productId);
        if (current != null) {
            current.addAndGet(quantity);
            observers.forEach(o -> o.onStockUpdated(productId, current.get()));
        }
    }

    public int getStock(String productId) {
        AtomicInteger qty = stock.get(productId);
        return qty != null ? qty.get() : 0;
    }
}

// โ”€โ”€โ”€ Stock Observer Interface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
interface StockObserver {
    void onStockUpdated(String productId, int newQty);
    void onOutOfStock(String productId);
}

// โ”€โ”€โ”€ Tax Calculator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class TaxCalculator {
    private final Map<String, Double> categoryTaxRates;

    public TaxCalculator() {
        categoryTaxRates = new HashMap<>();
        categoryTaxRates.put("electronics", 18.0);
        categoryTaxRates.put("clothing", 5.0);
        categoryTaxRates.put("food", 0.0);
        categoryTaxRates.put("furniture", 12.0);
    }

    public double calculateTax(CartItem item) {
        String category = item.getProduct().getCategory().toLowerCase();
        double rate = categoryTaxRates.getOrDefault(category, 18.0);
        return item.getSubtotal() * rate / 100.0;
    }

    public double calculateTotalTax(List<CartItem> items) {
        return items.stream().mapToDouble(this::calculateTax).sum();
    }
}

// โ”€โ”€โ”€ Order โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Order {
    private final String orderId;
    private final List<CartItem> items;
    private final double subtotal;
    private final double discount;
    private final double tax;
    private final double total;
    private final List<String> appliedCoupons;

    private Order(Builder builder) {
        this.orderId = builder.orderId;
        this.items = builder.items;
        this.subtotal = builder.subtotal;
        this.discount = builder.discount;
        this.tax = builder.tax;
        this.total = builder.total;
        this.appliedCoupons = builder.appliedCoupons;
    }

    public void printSummary() {
        System.out.println("\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—");
        System.out.println("โ•‘        ORDER SUMMARY                 โ•‘");
        System.out.println("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ");
        System.out.println("  Order ID: " + orderId);
        System.out.println("  Items:");
        for (CartItem item : items) {
            System.out.println("    - " + item);
        }
        System.out.println("  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
        System.out.printf("  Subtotal:   โ‚น%.2f%n", subtotal);
        System.out.printf("  Discount:  -โ‚น%.2f%n", discount);
        if (!appliedCoupons.isEmpty()) {
            System.out.println("  Coupons:    " + String.join(", ", appliedCoupons));
        }
        System.out.printf("  Tax (GST):  โ‚น%.2f%n", tax);
        System.out.println("  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€");
        System.out.printf("  TOTAL:      โ‚น%.2f%n", total);
        System.out.println("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
    }

    static class Builder {
        private String orderId;
        private List<CartItem> items = new ArrayList<>();
        private double subtotal;
        private double discount;
        private double tax;
        private double total;
        private List<String> appliedCoupons = new ArrayList<>();

        public Builder orderId(String id) { this.orderId = id; return this; }
        public Builder items(List<CartItem> items) { this.items = new ArrayList<>(items); return this; }
        public Builder subtotal(double s) { this.subtotal = s; return this; }
        public Builder discount(double d) { this.discount = d; return this; }
        public Builder tax(double t) { this.tax = t; return this; }
        public Builder total(double t) { this.total = t; return this; }
        public Builder appliedCoupons(List<String> c) { this.appliedCoupons = c; return this; }

        public Order build() {
            if (orderId == null) orderId = UUID.randomUUID().toString().substring(0, 10);
            return new Order(this);
        }
    }
}

// โ”€โ”€โ”€ Cart โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Cart {
    private final ConcurrentHashMap<String, CartItem> items;
    private final List<Coupon> appliedCoupons;
    private final StockService stockService;
    private final TaxCalculator taxCalculator;
    private final ReentrantLock lock;

    public Cart(StockService stockService) {
        this.items = new ConcurrentHashMap<>();
        this.appliedCoupons = new ArrayList<>();
        this.stockService = stockService;
        this.taxCalculator = new TaxCalculator();
        this.lock = new ReentrantLock();
    }

    public void addItem(Product product, int quantity) {
        lock.lock();
        try {
            if (quantity <= 0) throw new IllegalArgumentException("Quantity must be positive");

            if (!stockService.isAvailable(product.getId(), quantity)) {
                throw new RuntimeException("Insufficient stock for " + product.getName());
            }

            CartItem existing = items.get(product.getId());
            if (existing != null) {
                int newQty = existing.getQuantity() + quantity;
                if (!stockService.isAvailable(product.getId(), newQty)) {
                    throw new RuntimeException("Insufficient stock for total quantity");
                }
                existing.setQuantity(newQty);
            } else {
                items.put(product.getId(), new CartItem(product, quantity));
            }
        } finally {
            lock.unlock();
        }
    }

    public void removeItem(String productId) {
        lock.lock();
        try {
            CartItem removed = items.remove(productId);
            if (removed == null) {
                throw new RuntimeException("Product not in cart: " + productId);
            }
        } finally {
            lock.unlock();
        }
    }

    public void updateQuantity(String productId, int newQuantity) {
        lock.lock();
        try {
            CartItem item = items.get(productId);
            if (item == null) throw new RuntimeException("Product not in cart: " + productId);

            if (newQuantity <= 0) {
                items.remove(productId);
                return;
            }

            if (!stockService.isAvailable(productId, newQuantity)) {
                throw new RuntimeException("Insufficient stock");
            }
            item.setQuantity(newQuantity);
        } finally {
            lock.unlock();
        }
    }

    public void applyCoupon(Coupon coupon) {
        lock.lock();
        try {
            double subtotal = getSubtotal();
            if (!coupon.isApplicable(subtotal)) {
                throw new RuntimeException("Coupon not applicable. Min cart value not met.");
            }
            appliedCoupons.add(coupon);
        } finally {
            lock.unlock();
        }
    }

    public double getSubtotal() {
        return items.values().stream().mapToDouble(CartItem::getSubtotal).sum();
    }

    public Order checkout() {
        lock.lock();
        try {
            if (items.isEmpty()) {
                throw new RuntimeException("Cart is empty");
            }

            // Validate stock
            for (CartItem item : items.values()) {
                if (!stockService.isAvailable(item.getProduct().getId(), item.getQuantity())) {
                    throw new RuntimeException("Stock unavailable for: " + item.getProduct().getName());
                }
            }

            List<CartItem> itemList = new ArrayList<>(items.values());
            double subtotal = getSubtotal();

            // Apply discounts
            double totalDiscount = 0;
            List<String> couponCodes = new ArrayList<>();
            for (Coupon coupon : appliedCoupons) {
                totalDiscount += coupon.apply(itemList);
                couponCodes.add(coupon.getCode());
            }
            totalDiscount = Math.min(totalDiscount, subtotal);

            // Calculate tax on discounted amount
            double taxableAmount = subtotal - totalDiscount;
            double tax = taxCalculator.calculateTotalTax(itemList) *
                        (taxableAmount / subtotal);

            double total = taxableAmount + tax;

            // Reserve stock
            for (CartItem item : itemList) {
                stockService.reserve(item.getProduct().getId(), item.getQuantity());
            }

            // Build order
            Order order = new Order.Builder()
                .items(itemList)
                .subtotal(subtotal)
                .discount(totalDiscount)
                .tax(tax)
                .total(total)
                .appliedCoupons(couponCodes)
                .build();

            // Clear cart
            items.clear();
            appliedCoupons.clear();

            return order;
        } finally {
            lock.unlock();
        }
    }

    public int getItemCount() {
        return items.size();
    }

    public void displayCart() {
        System.out.println("\n--- Cart Contents ---");
        if (items.isEmpty()) {
            System.out.println("  (empty)");
            return;
        }
        for (CartItem item : items.values()) {
            System.out.println("  " + item);
        }
        System.out.printf("  Subtotal: โ‚น%.2f%n", getSubtotal());
        if (!appliedCoupons.isEmpty()) {
            System.out.println("  Coupons: " + appliedCoupons);
        }
    }
}

// โ”€โ”€โ”€ Main Demo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
public class EcommerceCartDemo {
    public static void main(String[] args) {
        System.out.println("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
        System.out.println("     E-COMMERCE CART - LLD DEMO       ");
        System.out.println("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n");

        // โ”€โ”€โ”€ Setup Stock โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        StockService stockService = new StockService();
        stockService.addObserver(new StockObserver() {
            public void onStockUpdated(String id, int qty) {
                System.out.println("  [STOCK] " + id + " updated to " + qty);
            }
            public void onOutOfStock(String id) {
                System.out.println("  [STOCK] " + id + " is OUT OF STOCK!");
            }
        });

        // Create products
        Product phone = new Product("P001", "iPhone 15", 79999.0, "electronics");
        Product shirt = new Product("P002", "Cotton Shirt", 999.0, "clothing");
        Product rice = new Product("P003", "Basmati Rice 5kg", 450.0, "food");
        Product laptop = new Product("P004", "MacBook Air", 114999.0, "electronics");

        stockService.setStock("P001", 10);
        stockService.setStock("P002", 50);
        stockService.setStock("P003", 100);
        stockService.setStock("P004", 5);

        // โ”€โ”€โ”€ Build Cart โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        Cart cart = new Cart(stockService);

        System.out.println("--- Adding Items to Cart ---");
        cart.addItem(phone, 1);
        cart.addItem(shirt, 3);
        cart.addItem(rice, 2);
        cart.displayCart();

        // โ”€โ”€โ”€ Update Quantity โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Updating Shirt Quantity to 5 ---");
        cart.updateQuantity("P002", 5);
        cart.displayCart();

        // โ”€โ”€โ”€ Apply Coupons โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Applying Coupons ---");
        Coupon percentOff = new Coupon("SAVE10", new PercentageDiscount(10), 1000);
        Coupon flatOff = new Coupon("FLAT500", new FlatDiscount(500), 5000);

        cart.applyCoupon(percentOff);
        System.out.println("Applied: " + percentOff);

        cart.applyCoupon(flatOff);
        System.out.println("Applied: " + flatOff);

        // โ”€โ”€โ”€ Remove Item โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Removing Rice ---");
        cart.removeItem("P003");
        cart.displayCart();

        // โ”€โ”€โ”€ Checkout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Checkout ---");
        Order order = cart.checkout();
        order.printSummary();

        // โ”€โ”€โ”€ Verify Cart is Empty After Checkout โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\nCart after checkout:");
        cart.displayCart();

        // โ”€โ”€โ”€ Try Out of Stock โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Out of Stock Test ---");
        try {
            cart.addItem(laptop, 10); // only 5 in stock
        } catch (RuntimeException e) {
            System.out.println("Expected: " + e.getMessage());
        }

        // โ”€โ”€โ”€ Buy X Get Y Demo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        System.out.println("\n--- Buy 2 Get 1 Free Demo ---");
        Cart cart2 = new Cart(stockService);
        cart2.addItem(shirt, 6); // buy 6 shirts
        Coupon bogo = new Coupon("B2G1", new BuyXGetYDiscount("P002", 2, 1), 0);
        cart2.applyCoupon(bogo);
        cart2.displayCart();
        Order order2 = cart2.checkout();
        order2.printSummary();

        System.out.println("\nโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
        System.out.println("           DEMO COMPLETE               ");
        System.out.println("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•");
    }
}
import threading
import uuid
from abc import ABC, abstractmethod
from typing import Optional

# โ”€โ”€โ”€ Product โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Product:
    def __init__(self, product_id: str, name: str, price: float, category: str):
        self._id = product_id
        self._name = name
        self._price = price
        self._category = category

    @property
    def id(self) -> str: return self._id
    @property
    def name(self) -> str: return self._name
    @property
    def price(self) -> float: return self._price
    @property
    def category(self) -> str: return self._category

    def __str__(self) -> str:
        return f"{self._name} (โ‚น{self._price:.2f})"

# โ”€โ”€โ”€ CartItem โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class CartItem:
    def __init__(self, product: Product, quantity: int):
        self._product = product
        self._quantity = quantity

    @property
    def product(self) -> Product: return self._product
    @property
    def quantity(self) -> int: return self._quantity

    @quantity.setter
    def quantity(self, value: int):
        if value < 0:
            raise ValueError("Quantity cannot be negative")
        self._quantity = value

    @property
    def subtotal(self) -> float:
        return self._product.price * self._quantity

    def __str__(self) -> str:
        return f"{self._product.name} x{self._quantity} = โ‚น{self.subtotal:.2f}"

# โ”€โ”€โ”€ Discount Strategy Interface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class DiscountStrategy(ABC):
    @abstractmethod
    def calculate_discount(self, items: list[CartItem]) -> float: pass

    @abstractmethod
    def description(self) -> str: pass

# โ”€โ”€โ”€ Percentage Discount โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class PercentageDiscount(DiscountStrategy):
    def __init__(self, percentage: float):
        if not 0 <= percentage <= 100:
            raise ValueError("Percentage must be 0-100")
        self._percentage = percentage

    def calculate_discount(self, items: list[CartItem]) -> float:
        subtotal = sum(item.subtotal for item in items)
        return subtotal * self._percentage / 100.0

    def description(self) -> str:
        return f"{self._percentage}% off"

# โ”€โ”€โ”€ Flat Discount โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class FlatDiscount(DiscountStrategy):
    def __init__(self, amount: float):
        self._amount = amount

    def calculate_discount(self, items: list[CartItem]) -> float:
        subtotal = sum(item.subtotal for item in items)
        return min(self._amount, subtotal)

    def description(self) -> str:
        return f"โ‚น{self._amount:.2f} off"

# โ”€โ”€โ”€ Buy X Get Y Free โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class BuyXGetYDiscount(DiscountStrategy):
    def __init__(self, product_id: str, buy_count: int, free_count: int):
        self._product_id = product_id
        self._buy_count = buy_count
        self._free_count = free_count

    def calculate_discount(self, items: list[CartItem]) -> float:
        for item in items:
            if item.product.id == self._product_id:
                qty = item.quantity
                sets = qty // (self._buy_count + self._free_count)
                free_items = sets * self._free_count
                return free_items * item.product.price
        return 0

    def description(self) -> str:
        return f"Buy {self._buy_count} Get {self._free_count} Free"

# โ”€โ”€โ”€ Coupon โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Coupon:
    def __init__(self, code: str, strategy: DiscountStrategy, min_cart_value: float = 0):
        self._code = code
        self._strategy = strategy
        self._min_cart_value = min_cart_value
        self._active = True

    @property
    def code(self) -> str: return self._code

    def is_applicable(self, cart_subtotal: float) -> bool:
        return self._active and cart_subtotal >= self._min_cart_value

    def apply(self, items: list[CartItem]) -> float:
        return self._strategy.calculate_discount(items)

    def __str__(self) -> str:
        return f"Coupon[{self._code}: {self._strategy.description()}]"

# โ”€โ”€โ”€ Stock Observer Interface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class StockObserver(ABC):
    @abstractmethod
    def on_stock_updated(self, product_id: str, new_qty: int): pass

    @abstractmethod
    def on_out_of_stock(self, product_id: str): pass

# โ”€โ”€โ”€ Stock Service โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class StockService:
    def __init__(self):
        self._stock: dict[str, int] = {}
        self._lock = threading.Lock()
        self._observers: list[StockObserver] = []

    def add_observer(self, observer: StockObserver):
        self._observers.append(observer)

    def set_stock(self, product_id: str, quantity: int):
        with self._lock:
            self._stock[product_id] = quantity

    def is_available(self, product_id: str, required_qty: int) -> bool:
        with self._lock:
            return self._stock.get(product_id, 0) >= required_qty

    def reserve(self, product_id: str, quantity: int) -> bool:
        with self._lock:
            current = self._stock.get(product_id, 0)
            if current < quantity:
                return False
            self._stock[product_id] = current - quantity
            if self._stock[product_id] == 0:
                for obs in self._observers:
                    obs.on_out_of_stock(product_id)
            return True

    def release(self, product_id: str, quantity: int):
        with self._lock:
            self._stock[product_id] = self._stock.get(product_id, 0) + quantity
            for obs in self._observers:
                obs.on_stock_updated(product_id, self._stock[product_id])

    def get_stock(self, product_id: str) -> int:
        with self._lock:
            return self._stock.get(product_id, 0)

# โ”€โ”€โ”€ Tax Calculator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class TaxCalculator:
    def __init__(self):
        self._category_rates = {
            "electronics": 18.0,
            "clothing": 5.0,
            "food": 0.0,
            "furniture": 12.0,
        }

    def calculate_tax(self, item: CartItem) -> float:
        category = item.product.category.lower()
        rate = self._category_rates.get(category, 18.0)
        return item.subtotal * rate / 100.0

    def calculate_total_tax(self, items: list[CartItem]) -> float:
        return sum(self.calculate_tax(item) for item in items)

# โ”€โ”€โ”€ Order โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Order:
    def __init__(self, order_id: str, items: list[CartItem], subtotal: float,
                 discount: float, tax: float, total: float, applied_coupons: list[str]):
        self.order_id = order_id
        self.items = items
        self.subtotal = subtotal
        self.discount = discount
        self.tax = tax
        self.total = total
        self.applied_coupons = applied_coupons

    def print_summary(self):
        print("\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—")
        print("โ•‘        ORDER SUMMARY                 โ•‘")
        print("โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ")
        print(f"  Order ID: {self.order_id}")
        print("  Items:")
        for item in self.items:
            print(f"    - {item}")
        print("  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€")
        print(f"  Subtotal:   โ‚น{self.subtotal:.2f}")
        print(f"  Discount:  -โ‚น{self.discount:.2f}")
        if self.applied_coupons:
            print(f"  Coupons:    {', '.join(self.applied_coupons)}")
        print(f"  Tax (GST):  โ‚น{self.tax:.2f}")
        print("  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€")
        print(f"  TOTAL:      โ‚น{self.total:.2f}")
        print("โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")

# โ”€โ”€โ”€ Order Builder โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class OrderBuilder:
    def __init__(self):
        self._order_id = uuid.uuid4().hex[:10]
        self._items = []
        self._subtotal = 0
        self._discount = 0
        self._tax = 0
        self._total = 0
        self._applied_coupons = []

    def order_id(self, oid: str): self._order_id = oid; return self
    def items(self, items: list[CartItem]): self._items = list(items); return self
    def subtotal(self, s: float): self._subtotal = s; return self
    def discount(self, d: float): self._discount = d; return self
    def tax(self, t: float): self._tax = t; return self
    def total(self, t: float): self._total = t; return self
    def applied_coupons(self, c: list[str]): self._applied_coupons = c; return self

    def build(self) -> Order:
        return Order(self._order_id, self._items, self._subtotal,
                     self._discount, self._tax, self._total, self._applied_coupons)

# โ”€โ”€โ”€ Cart โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Cart:
    def __init__(self, stock_service: StockService):
        self._items: dict[str, CartItem] = {}
        self._applied_coupons: list[Coupon] = []
        self._stock_service = stock_service
        self._tax_calculator = TaxCalculator()
        self._lock = threading.Lock()

    def add_item(self, product: Product, quantity: int):
        with self._lock:
            if quantity <= 0:
                raise ValueError("Quantity must be positive")
            if not self._stock_service.is_available(product.id, quantity):
                raise RuntimeError(f"Insufficient stock for {product.name}")

            if product.id in self._items:
                existing = self._items[product.id]
                new_qty = existing.quantity + quantity
                if not self._stock_service.is_available(product.id, new_qty):
                    raise RuntimeError("Insufficient stock for total quantity")
                existing.quantity = new_qty
            else:
                self._items[product.id] = CartItem(product, quantity)

    def remove_item(self, product_id: str):
        with self._lock:
            if product_id not in self._items:
                raise RuntimeError(f"Product not in cart: {product_id}")
            del self._items[product_id]

    def update_quantity(self, product_id: str, new_quantity: int):
        with self._lock:
            if product_id not in self._items:
                raise RuntimeError(f"Product not in cart: {product_id}")
            if new_quantity <= 0:
                del self._items[product_id]
                return
            if not self._stock_service.is_available(product_id, new_quantity):
                raise RuntimeError("Insufficient stock")
            self._items[product_id].quantity = new_quantity

    def apply_coupon(self, coupon: Coupon):
        with self._lock:
            if not coupon.is_applicable(self.subtotal):
                raise RuntimeError("Coupon not applicable. Min cart value not met.")
            self._applied_coupons.append(coupon)

    @property
    def subtotal(self) -> float:
        return sum(item.subtotal for item in self._items.values())

    def checkout(self) -> Order:
        with self._lock:
            if not self._items:
                raise RuntimeError("Cart is empty")

            for item in self._items.values():
                if not self._stock_service.is_available(item.product.id, item.quantity):
                    raise RuntimeError(f"Stock unavailable for: {item.product.name}")

            item_list = list(self._items.values())
            subtotal = self.subtotal

            total_discount = 0
            coupon_codes = []
            for coupon in self._applied_coupons:
                total_discount += coupon.apply(item_list)
                coupon_codes.append(coupon.code)
            total_discount = min(total_discount, subtotal)

            taxable_amount = subtotal - total_discount
            tax = self._tax_calculator.calculate_total_tax(item_list) * (taxable_amount / subtotal)
            total = taxable_amount + tax

            for item in item_list:
                self._stock_service.reserve(item.product.id, item.quantity)

            order = (OrderBuilder()
                     .items(item_list)
                     .subtotal(subtotal)
                     .discount(total_discount)
                     .tax(tax)
                     .total(total)
                     .applied_coupons(coupon_codes)
                     .build())

            self._items.clear()
            self._applied_coupons.clear()
            return order

    def display_cart(self):
        print("\n--- Cart Contents ---")
        if not self._items:
            print("  (empty)")
            return
        for item in self._items.values():
            print(f"  {item}")
        print(f"  Subtotal: โ‚น{self.subtotal:.2f}")

# โ”€โ”€โ”€ Main Demo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def main():
    print("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")
    print("     E-COMMERCE CART - LLD DEMO       ")
    print("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n")

    stock_service = StockService()

    class LoggingStockObserver(StockObserver):
        def on_stock_updated(self, product_id, new_qty):
            print(f"  [STOCK] {product_id} updated to {new_qty}")
        def on_out_of_stock(self, product_id):
            print(f"  [STOCK] {product_id} is OUT OF STOCK!")

    stock_service.add_observer(LoggingStockObserver())

    phone = Product("P001", "iPhone 15", 79999.0, "electronics")
    shirt = Product("P002", "Cotton Shirt", 999.0, "clothing")
    rice = Product("P003", "Basmati Rice 5kg", 450.0, "food")
    laptop = Product("P004", "MacBook Air", 114999.0, "electronics")

    stock_service.set_stock("P001", 10)
    stock_service.set_stock("P002", 50)
    stock_service.set_stock("P003", 100)
    stock_service.set_stock("P004", 5)

    cart = Cart(stock_service)

    print("--- Adding Items to Cart ---")
    cart.add_item(phone, 1)
    cart.add_item(shirt, 3)
    cart.add_item(rice, 2)
    cart.display_cart()

    print("\n--- Updating Shirt Quantity to 5 ---")
    cart.update_quantity("P002", 5)
    cart.display_cart()

    print("\n--- Applying Coupons ---")
    percent_off = Coupon("SAVE10", PercentageDiscount(10), 1000)
    flat_off = Coupon("FLAT500", FlatDiscount(500), 5000)
    cart.apply_coupon(percent_off)
    print(f"Applied: {percent_off}")
    cart.apply_coupon(flat_off)
    print(f"Applied: {flat_off}")

    print("\n--- Removing Rice ---")
    cart.remove_item("P003")
    cart.display_cart()

    print("\n--- Checkout ---")
    order = cart.checkout()
    order.print_summary()

    print("\nCart after checkout:")
    cart.display_cart()

    print("\n--- Out of Stock Test ---")
    try:
        cart.add_item(laptop, 10)
    except RuntimeError as e:
        print(f"Expected: {e}")

    print("\n--- Buy 2 Get 1 Free Demo ---")
    cart2 = Cart(stock_service)
    cart2.add_item(shirt, 6)
    bogo = Coupon("B2G1", BuyXGetYDiscount("P002", 2, 1), 0)
    cart2.apply_coupon(bogo)
    cart2.display_cart()
    order2 = cart2.checkout()
    order2.print_summary()

    print("\nโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")
    print("           DEMO COMPLETE               ")
    print("โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•")

if __name__ == "__main__":
    main()
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <memory>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <random>
#include <functional>
#include <atomic>

// โ”€โ”€โ”€ Generate ID โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
std::string generateId() {
    static std::mt19937 gen(std::random_device{}());
    static std::uniform_int_distribution<int> dist(0, 15);
    static const char* hex = "0123456789abcdef";
    std::string id;
    for (int i = 0; i < 10; ++i) id += hex[dist(gen)];
    return id;
}

// โ”€โ”€โ”€ Product โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Product {
private:
    std::string id, name, category;
    double price;
public:
    Product(std::string id, std::string name, double price, std::string category)
        : id(std::move(id)), name(std::move(name)), price(price), category(std::move(category)) {}

    const std::string& getId() const { return id; }
    const std::string& getName() const { return name; }
    double getPrice() const { return price; }
    const std::string& getCategory() const { return category; }
};

// โ”€โ”€โ”€ CartItem โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class CartItem {
private:
    std::shared_ptr<Product> product;
    int quantity;
public:
    CartItem(std::shared_ptr<Product> product, int quantity)
        : product(std::move(product)), quantity(quantity) {}

    const std::shared_ptr<Product>& getProduct() const { return product; }
    int getQuantity() const { return quantity; }
    void setQuantity(int q) { quantity = q; }
    double getSubtotal() const { return product->getPrice() * quantity; }

    std::string toString() const {
        std::ostringstream ss;
        ss << product->getName() << " x" << quantity
           << " = Rs" << std::fixed << std::setprecision(2) << getSubtotal();
        return ss.str();
    }
};

// โ”€โ”€โ”€ Discount Strategy โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class DiscountStrategy {
public:
    virtual ~DiscountStrategy() = default;
    virtual double calculateDiscount(const std::vector<CartItem>& items) = 0;
    virtual std::string getDescription() = 0;
};

class PercentageDiscount : public DiscountStrategy {
    double percentage;
public:
    PercentageDiscount(double pct) : percentage(pct) {}
    double calculateDiscount(const std::vector<CartItem>& items) override {
        double subtotal = 0;
        for (auto& item : items) subtotal += item.getSubtotal();
        return subtotal * percentage / 100.0;
    }
    std::string getDescription() override {
        return std::to_string((int)percentage) + "% off";
    }
};

class FlatDiscount : public DiscountStrategy {
    double amount;
public:
    FlatDiscount(double amt) : amount(amt) {}
    double calculateDiscount(const std::vector<CartItem>& items) override {
        double subtotal = 0;
        for (auto& item : items) subtotal += item.getSubtotal();
        return std::min(amount, subtotal);
    }
    std::string getDescription() override {
        std::ostringstream ss;
        ss << "Rs" << std::fixed << std::setprecision(2) << amount << " off";
        return ss.str();
    }
};

class BuyXGetYDiscount : public DiscountStrategy {
    std::string productId;
    int buyCount, freeCount;
public:
    BuyXGetYDiscount(std::string pid, int buy, int free)
        : productId(std::move(pid)), buyCount(buy), freeCount(free) {}
    double calculateDiscount(const std::vector<CartItem>& items) override {
        for (auto& item : items) {
            if (item.getProduct()->getId() == productId) {
                int qty = item.getQuantity();
                int sets = qty / (buyCount + freeCount);
                return sets * freeCount * item.getProduct()->getPrice();
            }
        }
        return 0;
    }
    std::string getDescription() override {
        return "Buy " + std::to_string(buyCount) + " Get " + std::to_string(freeCount) + " Free";
    }
};

// โ”€โ”€โ”€ Coupon โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Coupon {
    std::string code;
    std::shared_ptr<DiscountStrategy> strategy;
    double minCartValue;
    bool active;
public:
    Coupon(std::string code, std::shared_ptr<DiscountStrategy> strategy, double minVal)
        : code(std::move(code)), strategy(std::move(strategy)), minCartValue(minVal), active(true) {}

    bool isApplicable(double subtotal) { return active && subtotal >= minCartValue; }
    double apply(const std::vector<CartItem>& items) { return strategy->calculateDiscount(items); }
    const std::string& getCode() const { return code; }
    std::string getDescription() { return strategy->getDescription(); }
};

// โ”€โ”€โ”€ Stock Service โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class StockService {
    std::unordered_map<std::string, std::atomic<int>> stock;
    std::mutex mtx;
public:
    void setStock(const std::string& productId, int qty) {
        stock[productId] = qty;
    }
    bool isAvailable(const std::string& productId, int required) {
        auto it = stock.find(productId);
        return it != stock.end() && it->second.load() >= required;
    }
    bool reserve(const std::string& productId, int qty) {
        auto it = stock.find(productId);
        if (it == stock.end()) return false;
        int current = it->second.load();
        while (current >= qty) {
            if (it->second.compare_exchange_weak(current, current - qty)) {
                return true;
            }
        }
        return false;
    }
    int getStock(const std::string& productId) {
        auto it = stock.find(productId);
        return it != stock.end() ? it->second.load() : 0;
    }
};

// โ”€โ”€โ”€ Tax Calculator โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class TaxCalculator {
    std::unordered_map<std::string, double> rates;
public:
    TaxCalculator() {
        rates["electronics"] = 18.0;
        rates["clothing"] = 5.0;
        rates["food"] = 0.0;
        rates["furniture"] = 12.0;
    }
    double calculateTotalTax(const std::vector<CartItem>& items) {
        double total = 0;
        for (auto& item : items) {
            auto it = rates.find(item.getProduct()->getCategory());
            double rate = (it != rates.end()) ? it->second : 18.0;
            total += item.getSubtotal() * rate / 100.0;
        }
        return total;
    }
};

// โ”€โ”€โ”€ Order โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Order {
public:
    std::string orderId;
    std::vector<CartItem> items;
    double subtotal, discount, tax, total;
    std::vector<std::string> appliedCoupons;

    void printSummary() {
        std::cout << "\n+======================================+\n";
        std::cout << "|        ORDER SUMMARY                 |\n";
        std::cout << "+======================================+\n";
        std::cout << "  Order ID: " << orderId << "\n";
        std::cout << "  Items:\n";
        for (auto& item : items) {
            std::cout << "    - " << item.toString() << "\n";
        }
        std::cout << std::fixed << std::setprecision(2);
        std::cout << "  Subtotal:   Rs" << subtotal << "\n";
        std::cout << "  Discount:  -Rs" << discount << "\n";
        std::cout << "  Tax (GST):  Rs" << tax << "\n";
        std::cout << "  TOTAL:      Rs" << total << "\n";
        std::cout << "+======================================+\n";
    }
};

// โ”€โ”€โ”€ Cart โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
class Cart {
    std::unordered_map<std::string, CartItem> items;
    std::vector<std::shared_ptr<Coupon>> appliedCoupons;
    std::shared_ptr<StockService> stockService;
    TaxCalculator taxCalc;
    std::mutex mtx;

public:
    Cart(std::shared_ptr<StockService> ss) : stockService(std::move(ss)) {}

    void addItem(std::shared_ptr<Product> product, int qty) {
        std::lock_guard<std::mutex> lock(mtx);
        if (!stockService->isAvailable(product->getId(), qty))
            throw std::runtime_error("Insufficient stock for " + product->getName());

        auto it = items.find(product->getId());
        if (it != items.end()) {
            it->second.setQuantity(it->second.getQuantity() + qty);
        } else {
            items.emplace(product->getId(), CartItem(product, qty));
        }
    }

    void removeItem(const std::string& productId) {
        std::lock_guard<std::mutex> lock(mtx);
        items.erase(productId);
    }

    void updateQuantity(const std::string& productId, int newQty) {
        std::lock_guard<std::mutex> lock(mtx);
        if (newQty <= 0) { items.erase(productId); return; }
        auto it = items.find(productId);
        if (it != items.end()) it->second.setQuantity(newQty);
    }

    void applyCoupon(std::shared_ptr<Coupon> coupon) {
        std::lock_guard<std::mutex> lock(mtx);
        if (!coupon->isApplicable(getSubtotal()))
            throw std::runtime_error("Coupon not applicable");
        appliedCoupons.push_back(std::move(coupon));
    }

    double getSubtotal() {
        double total = 0;
        for (auto& [_, item] : items) total += item.getSubtotal();
        return total;
    }

    Order checkout() {
        std::lock_guard<std::mutex> lock(mtx);
        if (items.empty()) throw std::runtime_error("Cart is empty");

        std::vector<CartItem> itemList;
        for (auto& [_, item] : items) itemList.push_back(item);

        double subtotal = 0;
        for (auto& item : itemList) subtotal += item.getSubtotal();

        double totalDiscount = 0;
        std::vector<std::string> codes;
        for (auto& coupon : appliedCoupons) {
            totalDiscount += coupon->apply(itemList);
            codes.push_back(coupon->getCode());
        }
        totalDiscount = std::min(totalDiscount, subtotal);

        double taxable = subtotal - totalDiscount;
        double tax = taxCalc.calculateTotalTax(itemList) * (taxable / subtotal);
        double total = taxable + tax;

        for (auto& item : itemList) {
            stockService->reserve(item.getProduct()->getId(), item.getQuantity());
        }

        Order order;
        order.orderId = generateId();
        order.items = std::move(itemList);
        order.subtotal = subtotal;
        order.discount = totalDiscount;
        order.tax = tax;
        order.total = total;
        order.appliedCoupons = std::move(codes);

        items.clear();
        appliedCoupons.clear();
        return order;
    }

    void displayCart() {
        std::cout << "\n--- Cart Contents ---\n";
        if (items.empty()) { std::cout << "  (empty)\n"; return; }
        for (auto& [_, item] : items) {
            std::cout << "  " << item.toString() << "\n";
        }
        std::cout << std::fixed << std::setprecision(2)
                  << "  Subtotal: Rs" << getSubtotal() << "\n";
    }
};

// โ”€โ”€โ”€ Main Demo โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
int main() {
    std::cout << "=======================================\n";
    std::cout << "     E-COMMERCE CART - LLD DEMO       \n";
    std::cout << "=======================================\n\n";

    auto stockService = std::make_shared<StockService>();
    auto phone = std::make_shared<Product>("P001", "iPhone 15", 79999.0, "electronics");
    auto shirt = std::make_shared<Product>("P002", "Cotton Shirt", 999.0, "clothing");
    auto rice = std::make_shared<Product>("P003", "Basmati Rice 5kg", 450.0, "food");
    auto laptop = std::make_shared<Product>("P004", "MacBook Air", 114999.0, "electronics");

    stockService->setStock("P001", 10);
    stockService->setStock("P002", 50);
    stockService->setStock("P003", 100);
    stockService->setStock("P004", 5);

    Cart cart(stockService);

    std::cout << "--- Adding Items to Cart ---\n";
    cart.addItem(phone, 1);
    cart.addItem(shirt, 3);
    cart.addItem(rice, 2);
    cart.displayCart();

    std::cout << "\n--- Updating Shirt Quantity to 5 ---\n";
    cart.updateQuantity("P002", 5);
    cart.displayCart();

    std::cout << "\n--- Applying Coupons ---\n";
    auto percentOff = std::make_shared<Coupon>("SAVE10",
        std::make_shared<PercentageDiscount>(10), 1000);
    auto flatOff = std::make_shared<Coupon>("FLAT500",
        std::make_shared<FlatDiscount>(500), 5000);
    cart.applyCoupon(percentOff);
    cart.applyCoupon(flatOff);

    std::cout << "\n--- Removing Rice ---\n";
    cart.removeItem("P003");
    cart.displayCart();

    std::cout << "\n--- Checkout ---\n";
    Order order = cart.checkout();
    order.printSummary();

    std::cout << "\n--- Out of Stock Test ---\n";
    try {
        cart.addItem(laptop, 10);
    } catch (const std::runtime_error& e) {
        std::cout << "Expected: " << e.what() << "\n";
    }

    std::cout << "\n--- Buy 2 Get 1 Free Demo ---\n";
    Cart cart2(stockService);
    cart2.addItem(shirt, 6);
    auto bogo = std::make_shared<Coupon>("B2G1",
        std::make_shared<BuyXGetYDiscount>("P002", 2, 1), 0);
    cart2.applyCoupon(bogo);
    Order order2 = cart2.checkout();
    order2.printSummary();

    std::cout << "\n=======================================\n";
    std::cout << "           DEMO COMPLETE               \n";
    std::cout << "=======================================\n";
    return 0;
}

State Transitions

stateDiagram-v2
    [*] --> EMPTY
    EMPTY --> HAS_ITEMS : addItem
    HAS_ITEMS --> HAS_ITEMS : addItem / updateQty / applyCoupon
    HAS_ITEMS --> EMPTY : removeAll
    HAS_ITEMS --> CHECKOUT : checkout()
    CHECKOUT --> ORDER_PLACED : validation passes
    CHECKOUT --> HAS_ITEMS : validation fails
    ORDER_PLACED --> [*]

Sequence Diagram - Checkout Flow

sequenceDiagram
    participant Customer
    participant Cart
    participant Stock as StockService
    participant Tax as TaxCalculator
    participant OB as OrderBuilder

    Customer->>Cart: checkout()
    Cart->>Cart: validate not empty
    Cart->>Stock: isAvailable(productId, qty)
    Stock-->>Cart: true
    Cart->>Cart: calculate subtotal
    Cart->>Cart: apply coupon discounts
    Cart->>Tax: calculateTotalTax(items)
    Tax-->>Cart: tax amount
    Cart->>Stock: reserve(productId, qty)
    Stock-->>Cart: reserved
    Cart->>OB: build Order
    OB-->>Cart: Order
    Cart->>Cart: clear items
    Cart-->>Customer: Order

How to Extend

Extension Implementation
Wishlist Separate Wishlist class; move items to cart with one click
Cart expiry TTL per CartItem; background thread removes stale items and releases stock
Multi-currency New CurrencyConverter strategy injected into TaxCalculator
Loyalty points New LoyaltyDiscount implements DiscountStrategy
Saved carts Serialize cart to DB; reload on login
Bundle pricing New BundleDiscount strategy that checks if all bundle items present

What Interviewers Look For

  1. โœ… Strategy pattern for discounts - not if/else chains in Cart
  2. โœ… Stock validation before add and at checkout
  3. โœ… Thread-safety with locks on cart mutations
  4. โœ… Builder pattern for complex Order construction
  5. โœ… Observer for stock change notifications
  6. โœ… Coupon applicability check (min cart value)
  7. โœ… Tax calculation separated by category
  8. โœ… Accurate totals - discount cannot exceed subtotal


Scale this design past a single process and these are the concepts it runs into:

Discussion

Newest first
You

Free system design + DSA prep. If it helped you crack an interview, consider supporting.

SensAI SensAI
Beta
Listening...
Tap mic to stop voice mode

Shape what we build next

Every piece of feedback is read by the team and directly influences our roadmap.

What type of feedback?

Install SystemCraft

Add to your home screen for instant access, offline reading, and a distraction-free experience.

Offline reading Faster loads No browser tabs App-like feel

Unlock AI Features

One click to activate - no payment, no credit card. Just sign in and you're in.

AI code review and hints
SensAI chat assistant
AI mock interviews
Whiteboard analysis
100% free during early access