Designing an E-commerce Cart System
Difficulty: Intermediate Patterns: Strategy, Observer, Builder Asked at: Amazon, Flipkart, Myntra, Meesho
Functional Requirements
- Add/remove items - add products to cart, remove by product ID
- Quantity updates - increase or decrease quantity of items in cart
- Apply coupons/discounts - percentage off, flat discount, buy-X-get-Y
- Price calculation with tax - subtotal, discount, tax (GST), and final total
- Stock validation - verify product availability before adding and at checkout
- Checkout - validate cart, apply pricing, generate order summary
Non-Functional Requirements
- Thread-safety - concurrent add/remove operations must not corrupt cart state
- Extensibility - new discount types or tax rules require minimal code changes
- 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:
- Customer browses catalog, selects a product
- Client calls
cart.addItem(product, quantity) - Cart checks
StockService.isAvailable(productId, quantity) - If stock sufficient โ CartItem created/updated, stock reserved
- Customer applies coupon โ
cart.applyCoupon(coupon) - Coupon checks
minCartValueand applies its DiscountStrategy - Customer calls
cart.checkout() - Checkout validates all items still in stock
- TaxCalculator computes tax per item based on category
- OrderBuilder assembles final Order (subtotal - discount + tax = total)
- 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
- โ Strategy pattern for discounts - not if/else chains in Cart
- โ Stock validation before add and at checkout
- โ Thread-safety with locks on cart mutations
- โ Builder pattern for complex Order construction
- โ Observer for stock change notifications
- โ Coupon applicability check (min cart value)
- โ Tax calculation separated by category
- โ Accurate totals - discount cannot exceed subtotal
Related Concepts
Scale this design past a single process and these are the concepts it runs into:
- Caching โ โ product and price reads dominate cart traffic, so where the cache sits and how it is invalidated decides correctness
- Idempotency โ โ a double-clicked checkout must produce one order, not two
- Transactions & Isolation Levels โ โ the stock check at checkout is a read-modify-write that oversells under concurrency
- Saga Pattern โ โ checkout spans cart, stock and payment; a failure mid-way needs compensating rollbacks
Discussion
Newest first