Skip to content
advancedPhase 52 · HLD Case Studies

E-commerce

Design an e-commerce platform with cart, checkout, and inventory.

2h
0 problems
Topic Progress0%

Requirements & Scope

Functional Requirements

  • Browse products: View product listings by category, featured, deals
  • Search/filter: Full-text search with filters (price, brand, rating, availability)
  • Add to cart: Add items, modify quantities, apply coupons
  • Checkout: Payment processing, address selection, delivery options
  • Track orders: Real-time order status updates
  • Reviews/ratings: Post-purchase reviews and ratings

Non-Functional Requirements

  • High availability: 99.99% uptime, multi-region deployment
  • Fast search: Search results in under 200ms
  • Inventory consistency: Prevent overselling, real-time stock updates
  • Flash sale handling: Support 100x traffic spikes during sales
  • Scalability: Handle billions of products and millions of concurrent users

Scale Estimation

Metric Daily Per Second
Product views 2B ~23K
Search queries 500M ~6K
Orders 10M ~115
Cart operations 100M ~1.2K
Reviews 1M ~12

Core Entities

  • Product, ProductVariant, Category, Brand, User, Cart, CartItem, Order, OrderItem, Inventory, Review, Address, Payment

Cart & Checkout

Cart Service

@Service
public class CartService {
    @Autowired private CartRepository cartRepo;
    @Autowired private CacheService cacheService;
    
    private static final Duration CART_TTL = Duration.ofDays(30);
    
    public Cart getCart(String userId) {
        String cacheKey = "cart:" + userId;
        
        return cacheService.getOrLoad(cacheKey, () -> {
            return cartRepo.findByUserId(userId)
                .orElseGet(() -> createNewCart(userId));
        }, CART_TTL);
    }
    
    @Transactional
    public Cart addToCart(String userId, AddToCartRequest request) {
        Cart cart = getCart(userId);
        
        // Check inventory before adding
        Inventory inventory = inventoryService.checkStock(
            request.getProductId(), request.getQuantity());
        if (!inventory.isAvailable()) {
            throw new InsufficientStockException();
        }
        
        // Check if item already in cart
        Optional<CartItem> existingItem = cart.getItems().stream()
            .filter(item -> item.getProductId().equals(request.getProductId())
                && item.getVariantId().equals(request.getVariantId()))
            .findFirst();
        
        if (existingItem.isPresent()) {
            CartItem item = existingItem.get();
            int newQuantity = item.getQuantity() + request.getQuantity();
            
            // Validate total quantity against stock
            if (newQuantity > inventory.getAvailableQuantity()) {
                throw new InsufficientStockException();
            }
            
            item.setQuantity(newQuantity);
            item.setUpdatedAt(Instant.now());
        } else {
            cart.getItems().add(CartItem.builder()
                .productId(request.getProductId())
                .variantId(request.getVariantId())
                .quantity(request.getQuantity())
                .addedAt(Instant.now())
                .build());
        }
        
        cart.setUpdatedAt(Instant.now());
        cartRepo.save(cart);
        
        // Invalidate cache
        cacheService.invalidate("cart:" + userId);
        
        return cart;
    }
    
    @Transactional
    public Cart updateQuantity(String userId, Long itemId, int newQuantity) {
        Cart cart = getCart(userId);
        
        CartItem item = cart.getItems().stream()
            .filter(i -> i.getId().equals(itemId))
            .findFirst()
            .orElseThrow(() -> new CartItemNotFoundException());
        
        if (newQuantity <= 0) {
            cart.getItems().remove(item);
        } else {
            // Validate stock
            Inventory inventory = inventoryService.checkStock(
                item.getProductId(), newQuantity);
            if (!inventory.isAvailable()) {
                throw new InsufficientStockException();
            }
            item.setQuantity(newQuantity);
            item.setUpdatedAt(Instant.now());
        }
        
        cart.setUpdatedAt(Instant.now());
        cartRepo.save(cart);
        cacheService.invalidate("cart:" + userId);
        
        return cart;
    }
}

Checkout Flow

@Service
public class CheckoutService {
    @Autowired private OrderService orderService;
    @Autowired private PaymentService paymentService;
    @Autowired private InventoryService inventoryService;
    @Autowired private ShippingService shippingService;
    
    @Transactional
    public Order checkout(CheckoutRequest request) {
        String userId = request.getUserId();
        Cart cart = cartService.getCart(userId);
        
        if (cart.isEmpty()) {
            throw new EmptyCartException();
        }
        
        // 1. Reserve inventory (prevents overselling)
        List<InventoryReservation> reservations = new ArrayList<>();
        try {
            for (CartItem item : cart.getItems()) {
                InventoryReservation reservation = inventoryService
                    .reserve(item.getProductId(), item.getQuantity());
                reservations.add(reservation);
            }
        } catch (InsufficientStockException e) {
            // Rollback all reservations
            reservations.forEach(r -> inventoryService.release(r.getId()));
            throw e;
        }
        
        // 2. Calculate totals
        BigDecimal subtotal = calculateSubtotal(cart.getItems());
        BigDecimal shipping = shippingService.calculateShipping(
            cart.getItems(), request.getShippingAddress());
        BigDecimal tax = calculateTax(subtotal, request.getShippingAddress());
        BigDecimal discount = applyCoupons(cart.getCouponCodes(), subtotal);
        BigDecimal total = subtotal.add(shipping).add(tax).subtract(discount);
        
        // 3. Create order
        Order order = Order.builder()
            .userId(userId)
            .items(convertToOrderItems(cart.getItems()))
            .subtotal(subtotal)
            .shipping(shipping)
            .tax(tax)
            .discount(discount)
            .total(total)
            .shippingAddress(request.getShippingAddress())
            .status(OrderStatus.PLACED)
            .build();
        
        order = orderService.createOrder(order);
        
        // 4. Process payment with idempotency
        PaymentResult payment = paymentService.charge(
            order.getId(), total, request.getPaymentMethod(),
            request.getIdempotencyKey());
        
        if (!payment.isSuccessful()) {
            // Release inventory reservations
            reservations.forEach(r -> inventoryService.release(r.getId()));
            orderService.updateStatus(order.getId(), OrderStatus.PAYMENT_FAILED);
            throw new PaymentFailedException(payment.getErrorMessage());
        }
        
        // 5. Confirm order and inventory
        reservations.forEach(r -> inventoryService.confirm(r.getId()));
        orderService.updateStatus(order.getId(), OrderStatus.CONFIRMED);
        
        // 6. Clear cart
        cartService.clearCart(userId);
        
        // 7. Send confirmation
        notificationService.sendOrderConfirmation(order);
        
        return order;
    }
}

Payment Data Model

CREATE TABLE payments (
    id BIGINT PRIMARY KEY,
    order_id BIGINT REFERENCES orders(id),
    amount DECIMAL(10,2),
    currency VARCHAR(3),
    status ENUM('PENDING','PROCESSING','COMPLETED','FAILED','REFUNDED'),
    payment_method VARCHAR(50),
    stripe_payment_id VARCHAR(255),
    idempotency_key VARCHAR(255) UNIQUE,
    created_at TIMESTAMP,
    completed_at TIMESTAMP,
    INDEX idx_order (order_id),
    INDEX idx_idempotency (idempotency_key)
);

Order Management & Fulfillment

Order State Machine

PLACED → CONFIRMED → PROCESSING → SHIPPED → DELIVERED → COMPLETED
   ↓          ↓           ↓           ↓          ↓          ↓
CANCELLED  CANCELLED   CANCELLED   RETURNED  RETURNED   RATED

Inventory Management

@Service
public class InventoryService {
    @Autowired private InventoryRepository inventoryRepo;
    @Autowired private CacheService cacheService;
    @Autowired private KafkaTemplate<String, String> kafkaTemplate;
    
    public InventoryReservation reserve(Long productId, int quantity) {
        // Use pessimistic locking to prevent overselling
        Inventory inventory = inventoryRepo.findByIdWithLock(productId)
            .orElseThrow(() -> new InventoryNotFoundException(productId));
        
        int available = inventory.getQuantity() - inventory.getReserved();
        if (available < quantity) {
            throw new InsufficientStockException(productId, quantity, available);
        }
        
        inventory.setReserved(inventory.getReserved() + quantity);
        inventoryRepo.save(inventory);
        
        InventoryReservation reservation = InventoryReservation.builder()
            .productId(productId)
            .quantity(quantity)
            .status(ReservationStatus.ACTIVE)
            .expiresAt(Instant.now().plus(Duration.ofMinutes(15)))
            .build();
        
        inventoryRepo.saveReservation(reservation);
        
        // Invalidate cache
        cacheService.invalidate("stock:" + productId);
        
        return reservation;
    }
    
    public void confirm(InventoryReservation reservation) {
        Inventory inventory = inventoryRepo.findById(reservation.getProductId())
            .orElseThrow();
        
        inventory.setQuantity(inventory.getQuantity() - reservation.getQuantity());
        inventory.setReserved(inventory.getReserved() - reservation.getQuantity());
        inventoryRepo.save(inventory);
        
        reservation.setStatus(ReservationStatus.CONFIRMED);
        inventoryRepo.saveReservation(reservation);
        
        cacheService.invalidate("stock:" + reservation.getProductId());
    }
    
    public void release(InventoryReservation reservation) {
        Inventory inventory = inventoryRepo.findById(reservation.getProductId())
            .orElseThrow();
        
        inventory.setReserved(inventory.getReserved() - reservation.getQuantity());
        inventoryRepo.save(inventory);
        
        reservation.setStatus(ReservationStatus.RELEASED);
        inventoryRepo.saveReservation(reservation);
        
        cacheService.invalidate("stock:" + reservation.getProductId());
    }
    
    // Scheduled job to expire old reservations
    @Scheduled(fixedRate = 60000)
    public void expireStaleReservations() {
        List<InventoryReservation> expired = inventoryRepo
            .findExpiredReservations(Instant.now());
        
        for (InventoryReservation reservation : expired) {
            release(reservation);
            kafkaTemplate.send("inventory-releases", 
                reservation.getProductId().toString());
        }
    }
}

Flash Sale Handling

@Service
public class FlashSaleService {
    @Autowired private QueueService queueService;
    @Autowired private InventoryService inventoryService;
    @Autowired private CacheService cacheService;
    
    private static final int VIRTUAL_WAITING_ROOM_SIZE = 10000;
    
    public FlashSaleResult processFlashSaleOrder(FlashSaleOrderRequest request) {
        Long productId = request.getProductId();
        
        // 1. Check if product is in flash sale
        FlashSale sale = cacheService.get("flashsale:" + productId);
        if (sale == null || !sale.isActive()) {
            throw new NotInFlashSaleException(productId);
        }
        
        // 2. Check inventory from cache (fast check)
        Integer stock = cacheService.get("flashstock:" + productId);
        if (stock == null || stock <= 0) {
            return FlashSaleResult.soldOut();
        }
        
        // 3. Add to virtual waiting room queue
        String queueId = queueService.enqueue("flashsale:" + productId, 
            request.getUserId(),
            Duration.ofMinutes(10));
        
        // 4. Wait in queue (polling or WebSocket notification)
        int position = queueService.getPosition(queueId);
        if (position > VIRTUAL_WAITING_ROOM_SIZE) {
            return FlashSaleResult.queueFull(position);
        }
        
        // 5. When it's user's turn, process order
        try {
            InventoryReservation reservation = inventoryService
                .reserve(productId, request.getQuantity());
            
            // 6. Create order directly (skip normal cart flow)
            Order order = orderService.createFlashSaleOrder(request, reservation);
            
            // 7. Process payment
            paymentService.charge(order.getId(), order.getTotal(), 
                request.getPaymentMethod(), request.getIdempotencyKey());
            
            return FlashSaleResult.success(order);
        } catch (InsufficientStockException e) {
            return FlashSaleResult.soldOut();
        } finally {
            queueService.dequeue(queueId);
        }
    }
}

Order Data Model

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT REFERENCES users(id),
    status ENUM('PLACED','CONFIRMED','PROCESSING','SHIPPED',
                'DELIVERED','COMPLETED','CANCELLED','RETURNED'),
    subtotal DECIMAL(10,2),
    shipping DECIMAL(10,2),
    tax DECIMAL(10,2),
    discount DECIMAL(10,2),
    total DECIMAL(10,2),
    shipping_address JSON,
    billing_address JSON,
    created_at TIMESTAMP,
    updated_at TIMESTAMP,
    shipped_at TIMESTAMP,
    delivered_at TIMESTAMP,
    INDEX idx_user (user_id, created_at),
    INDEX idx_status (status)
);

CREATE TABLE order_items (
    id BIGINT PRIMARY KEY,
    order_id BIGINT REFERENCES orders(id),
    product_id BIGINT REFERENCES products(id),
    variant_id BIGINT REFERENCES product_variants(id),
    quantity INT,
    unit_price DECIMAL(10,2),
    total_price DECIMAL(10,2)
);

CREATE TABLE inventory (
    product_id BIGINT PRIMARY KEY REFERENCES products(id),
    quantity INT,
    reserved INT DEFAULT 0,
    version INT DEFAULT 0,
    INDEX idx_available (quantity, reserved)
);

Scaling Strategy

  • Database sharding: Shard by product category or user ID
  • Read replicas: Product catalog and order history on read replicas
  • CDN: Product images served from CDN (CloudFront)
  • Caching: Product details, search results, inventory in Redis
  • Async processing: Order confirmation emails, inventory updates via Kafka

Practice Problems

0/3solved
Design E-Commerce Platform (Design Amazon) System

Design a scalable E-Commerce Platform (Design Amazon) system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
E-Commerce Platform (Design Amazon) Scaling

How would you scale E-Commerce Platform (Design Amazon) to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
E-Commerce Platform (Design Amazon) Failure Modes

Analyze potential failure modes for E-Commerce Platform (Design Amazon) and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. How should inventory be managed during checkout to prevent overselling?

Question 1 options

2. What is the best way to implement product search with filters?

Question 2 options

3. How should a flash sale handle 100x traffic spike?

Question 3 options

4. What happens if payment fails after inventory is reserved?

Question 4 options

5. Where should product images be stored?

Question 5 options

Flashcards

Question

How does inventory reservation prevent overselling?

Answer

Reserve stock before payment (increment reserved count), confirm after payment (decrement quantity and reserved), release on failure or timeout (decrement reserved).

Question

What search technology should be used for product catalog?

Answer

Elasticsearch for full-text search, filters, aggregations. MySQL for transactional product data. Sync via CDC or async updates.

Question

How does a virtual waiting room work?

Answer

Users are queued when entering sale. Process orders sequentially from queue. Prevents database overload and ensures fair access.

Question

What is idempotency in payment processing?

Answer

Each payment request has a unique idempotency key. If retried (network error), server returns same result without charging twice.

Question

Where should cart data be stored?

Answer

Redis for session-based carts (fast, temporary). Database for persistent carts (linked to user account). TTL-based expiry for abandoned carts.

Question

How should product images be served?

Answer

Store in S3, serve via CloudFront CDN. Generate thumbnails for listing pages. Lazy load images on product detail pages.

Question

What is the checkout order state machine?

Answer

PLACED → CONFIRMED → PROCESSING → SHIPPED → DELIVERED → COMPLETED. Each state transition triggers specific actions and notifications.

Revision Notes

Key Takeaways

  • 1.Inventory reservation is critical to prevent overselling
  • 2.Elasticsearch provides fast search with complex filters
  • 3.Virtual waiting room handles flash sale traffic spikes
  • 4.Idempotency prevents duplicate charges in payments
  • 5.CDN and caching reduce latency for product images
  • 6.Database sharding enables horizontal scaling

Interview Tips

  • Start with functional requirements and scale estimation
  • Draw high-level architecture with all microservices
  • Explain inventory reservation flow in detail
  • Discuss search architecture (MySQL + Elasticsearch)
  • Explain flash sale handling with virtual waiting room
  • Mention payment idempotency for reliability
  • Be ready to discuss database schema and indexing

Cheat Sheet

E-Commerce Platform - Key Points

Architecture Components

  • Product Service: Catalog management, pricing
  • Search Service: Elasticsearch for full-text search and filters
  • Cart Service: Session/persistent cart management
  • Order Service: Order lifecycle, state machine
  • Inventory Service: Stock management, reservations
  • Payment Service: Payment processing, idempotency

Key Design Decisions

  1. Search: Elasticsearch for fast full-text search with filters
  2. Inventory: Reservation system prevents overselling
  3. Cart: Redis for fast access, database for persistence
  4. Flash Sales: Virtual waiting room + queue-based ordering
  5. Images: S3 + CDN for scalable media delivery

Scale Numbers

  • 2B daily product views (~23K/sec)
  • 500M search queries/day (~6K/sec)
  • 10M daily orders (~115/sec)
  • 100M cart operations/day (~1.2K/sec)

Inventory Flow

Reserve → Payment → Confirm → Decrement
Reserve → Payment Fail → Release
Reserve → Timeout (15min) → Release