Machine Coding Problem

Inventory Management

maco30maco60macoAllcommercetwo-phase-reservationttl-cleanupobserver-patternthread-safe-inventory-decrements
Commonly Asked By:AmazonWalmartShopify

Requirements & System Scope

Functional Scope (In-Scope)

  • Two-Phase Inventory Lock: Implement soft stock reservations (RESERVE) that roll back if checkout fails or expires (TTL).
  • Zero Overselling: Guarantee thread safety during high volume concurrent purchases.
  • Automated TTL Cleanup: Free up expired reservations asynchronously using highly efficient background workers.
  • Stock Shortage Notifications (Observer Pattern): Watch threshold limits and trigger low-stock alerts to observers.

Explicit Boundaries (Out-of-Scope)

  • No Complex Shipping logistics API: Excludes tracking warehouse labels, shipping containers, or coordinate tracking.
  • No Automatic Purchase Order (PO) Builder: System design handles item counts; does not write real-world legal purchase contracts.

Class Diagram & Entity Relationships

Structural layout showing relationships between products, reservations, and inventory events:

Loading...
  • Atomic Product Items: Product handles counts for available vs reserved items behind thread-safe structures.
  • Observer updates: Stock level warnings are dispatched cleanly using decouple interfaces.

Design Patterns & SOLID Principles

  • Two-Phase Commit Pattern (Soft hold): Decoupling reservations from concrete purchases satisfies data validation flows (SRP), ensuring items are not sold to multiple concurrent checkout threads.
  • Observer Pattern (Stock Alerting): Attaching observers to monitor stock boundaries decouples messaging triggers from the core checkout pathways.

Core Execution Workflows

Checkout & Hold Lifecycle

  1. Rider initiates checkout: reserveStock(resId, productId, qty, ttlMs).
  2. Fetch product, evaluate inventory availability:
    1. If available quantity >= requested quantity, decrement available stock and increment reserved stock.
    2. Write new Reservation entry mapping the expiry time.
  3. If user completes checkout: confirmPurchase(resId).
    1. Deduct count from reserved stock balance. Remove reservation from register map.
  4. If user fails to checkout, background loop detects expiry:
    1. Remove reservation, add quantity back to available stock pool.

Concurrency & Thread Safety Strategy

Flash sales can route thousands of concurrent requests to purchase the same inventory items:

  • Fine-Grained synchronized Monitors: Lock individual Product instances rather than global maps to limit thread bottlenecks.
  • Thread-Safe Map Registries: Keep reservation lookups safe inside robust ConcurrentHashMap structures.

Complete Clean Code Blueprint

Practical reference implementations showing transactional inventory checkouts in Java and Python:

// โ”€โ”€โ”€ JAVA BLUEPRINT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

interface InventoryObserver {
    void onLowStock(String productId, int availableQty);
}

class RestockAlertSystem implements InventoryObserver {
    @Override
    public void onLowStock(String productId, int availableQty) {
        System.out.println(String.format("[Alert] Product %s is running low on stock! Current: %d", productId, availableQty));
    }
}

class Product {
    private final String id;
    private final String name;
    private int availableQty;
    private int reservedQty;
    private final int lowStockThreshold;
    private final List<InventoryObserver> observers = new CopyOnWriteArrayList<>();
    private final ReentrantLock lock = new ReentrantLock();

    public Product(String id, String name, int initialQty, int threshold) {
        this.id = id;
        this.name = name;
        this.availableQty = initialQty;
        this.reservedQty = 0;
        this.lowStockThreshold = threshold;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    
    public int getAvailableQty() {
        lock.lock();
        try { return availableQty; } finally { lock.unlock(); }
    }

    public void addObserver(InventoryObserver obs) { observers.add(obs); }

    public boolean reserve(int qty) {
        lock.lock();
        try {
            if (availableQty >= qty) {
                availableQty -= qty;
                reservedQty += qty;
                checkThreshold();
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public void confirm(int qty) {
        lock.lock();
        try {
            reservedQty = Math.max(0, reservedQty - qty);
        } finally {
            lock.unlock();
        }
    }

    public void release(int qty) {
        lock.lock();
        try {
            reservedQty = Math.max(0, reservedQty - qty);
            availableQty += qty;
        } finally {
            lock.unlock();
        }
    }

    public void restock(int qty) {
        lock.lock();
        try {
            availableQty += qty;
        } finally {
            lock.unlock();
        }
    }

    private void checkThreshold() {
        if (availableQty <= lowStockThreshold) {
            for (InventoryObserver obs : observers) {
                obs.onLowStock(id, availableQty);
            }
        }
    }
}

class Reservation {
    private final String id;
    private final String productId;
    private final int quantity;
    private final long expiryTime;

    public Reservation(String id, String productId, int qty, long ttlMs) {
        this.id = id;
        this.productId = productId;
        this.quantity = qty;
        this.expiryTime = System.currentTimeMillis() + ttlMs;
    }

    public String getId() { return id; }
    public String getProductId() { return productId; }
    public int getQuantity() { return quantity; }
    public boolean isExpired() { return System.currentTimeMillis() > expiryTime; }
}

class InventoryService {
    private final Map<String, Product> products = new ConcurrentHashMap<>();
    private final Map<String, Reservation> reservations = new ConcurrentHashMap<>();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();

    public InventoryService() {
        scheduler.scheduleAtFixedRate(this::cleanExpiredReservations, 100, 100, TimeUnit.MILLISECONDS);
    }

    public void addProduct(Product p) { products.put(p.getId(), p); }

    public boolean reserveStock(String reservationId, String productId, int qty, long ttlMs) {
        Product p = products.get(productId);
        if (p != null && p.reserve(qty)) {
            reservations.put(reservationId, new Reservation(reservationId, productId, qty, ttlMs));
            return true;
        }
        return false;
    }

    public boolean confirmPurchase(String reservationId) {
        Reservation res = reservations.remove(reservationId);
        if (res != null && !res.isExpired()) {
            Product p = products.get(res.getProductId());
            if (p != null) {
                p.confirm(res.getQuantity());
                System.out.println("[Service] Purchase confirmed for Reservation: " + reservationId);
                return true;
            }
        }
        return false;
    }

    private void cleanExpiredReservations() {
        for (Reservation res : reservations.values()) {
            if (res.isExpired()) {
                if (reservations.remove(res.getId()) != null) {
                    Product p = products.get(res.getProductId());
                    if (p != null) {
                        p.release(res.getQuantity());
                        System.out.println(String.format("[TTL Cleanup] Released expired Reservation %s (%d units of %s)",
                                res.getId(), res.getQuantity(), p.getName()));
                    }
                }
            }
        }
    }

    public void shutdown() {
        scheduler.shutdown();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Inventory Management Simulation ===");
        InventoryService service = new InventoryService();
        RestockAlertSystem alertSystem = new RestockAlertSystem();

        Product laptop = new Product("PROD-01", "MacBook Pro", 6, 2);
        laptop.addObserver(alertSystem);
        service.addProduct(laptop);

        // Reserve Stock - Success
        boolean r1 = service.reserveStock("RES-101", "PROD-01", 3, 200); // 200ms expiry
        System.out.println("Reservation RES-101: " + (r1 ? "SUCCESS" : "FAILED"));

        // Reserve Stock - Triggers Low Stock Alert
        boolean r2 = service.reserveStock("RES-102", "PROD-01", 2, 1000);
        System.out.println("Reservation RES-102: " + (r2 ? "SUCCESS" : "FAILED"));

        // Let RES-101 expire (TTL)
        Thread.sleep(300);

        // Confirm RES-102 Purchase
        service.confirmPurchase("RES-102");

        service.shutdown();
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...