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

Inventory Management System

Difficulty: Intermediate Patterns: Repository, Service Layer, Factory Asked at: Amazon, Flipkart, Walmart, Swiggy


Functional Requirements

  1. Add products - register products with SKU, name, category, and price
  2. Multi-warehouse stock - track stock quantities per product per warehouse
  3. Add/Remove stock - thread-safe stock level mutations with validation
  4. Query stock - get total stock or stock at a specific warehouse
  5. Atomic transfers - move stock between warehouses as a single atomic operation

Non-Functional Requirements

  1. Thread-safety - concurrent stock mutations must not corrupt data (per-item locks)
  2. Deadlock prevention - lock ordering by warehouse ID for multi-lock operations
  3. Extensibility - repository interfaces allow swapping storage without changing logic
  4. Validation - custom exceptions for clear error semantics

Core Entities

Entity Description
Product Immutable product info - SKU, name, category, price
Warehouse Storage location with ID, name, and physical location
InventoryItem Stock record for a product at a warehouse, with per-item lock
ProductRepository Interface for product CRUD operations
WarehouseRepository Interface for warehouse CRUD operations
InventoryRepository Interface for inventory item storage and queries
InventoryService Facade orchestrating all stock operations with thread-safety

Class Diagram

classDiagram
    class Product {
        -String sku
        -String name
        -String category
        -double price
        +getSku() String
        +getName() String
        +getCategory() String
        +getPrice() double
    }

    class Warehouse {
        -String id
        -String name
        -String location
        +getId() String
        +getName() String
    }

    class InventoryItem {
        -String sku
        -String warehouseId
        -int quantity
        -ReentrantLock lock
        +addStock(int quantity)
        +removeStock(int quantity)
        +getQuantity() int
    }

    class ProductRepository {
        <<interface>>
        +save(Product)
        +findBySku(String) Product
        +existsBySku(String) boolean
    }

    class WarehouseRepository {
        <<interface>>
        +save(Warehouse)
        +findById(String) Warehouse
        +existsById(String) boolean
    }

    class InventoryRepository {
        <<interface>>
        +save(InventoryItem)
        +find(String sku and String warehouseId) InventoryItem
        +findAllBySku(String) List of InventoryItem
    }

    class InventoryService {
        -ProductRepository productRepo
        -WarehouseRepository warehouseRepo
        -InventoryRepository inventoryRepo
        +addProduct(String sku and String name and String category and double price)
        +addStock(String sku and String warehouseId and int qty)
        +removeStock(String sku and String warehouseId and int qty)
        +getStock(String sku) int
        +getStockByWarehouse(String sku and String warehouseId) int
        +transferStock(String sku and String from and String to and int qty)
    }

    class InsufficientStockException {
        -String sku
        -String warehouseId
        -int requested
        -int available
    }

    class ProductNotFoundException {
        -String sku
    }

    class WarehouseNotFoundException {
        -String warehouseId
    }

    InventoryService --> ProductRepository
    InventoryService --> WarehouseRepository
    InventoryService --> InventoryRepository
    InventoryRepository --> InventoryItem
    ProductRepository --> Product
    WarehouseRepository --> Warehouse
    InventoryItem --> Product
    InventoryItem --> Warehouse

Design Patterns

Pattern Where Why
Repository ProductRepository, WarehouseRepository, InventoryRepository Abstracts storage; swap in-memory for DB without changing business logic
Service Layer InventoryService Single entry point for all operations; encapsulates validation, locking, and coordination
Factory Product and InventoryItem creation Centralized construction ensures valid state

How It All Fits Together

Hereโ€™s what happens when a user calls transferStock(sku, fromWarehouse, toWarehouse, quantity):

  1. InventoryService validates that the product and both warehouses exist
  2. Retrieves or creates InventoryItems for both source and destination
  3. Determines lock ordering by comparing warehouse IDs lexicographically
  4. Acquires locks in consistent order (prevents deadlocks when concurrent transfers go Aโ†’B and Bโ†’A)
  5. Under both locks: validates source has sufficient stock
  6. Deducts from source and adds to destination atomically
  7. Releases locks in reverse order

๐Ÿ’ก This lock-ordering trick is the standard solution to the dining philosophers problem. By always acquiring locks in the same global order, circular wait is impossible.


Complete Code

Product and Warehouse

Immutable domain model classes representing the core entities. Product holds catalog information (SKU, name, category, price) while Warehouse represents a physical storage location.

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

class Product {
    private final String sku;
    private final String name;
    private final String category;
    private final double price;

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

    public String getSku() { return sku; }
    public String getName() { return name; }
    public String getCategory() { return category; }
    public double getPrice() { return price; }

    @Override
    public String toString() {
        return name + " [" + sku + "] $" + price;
    }
}

class Warehouse {
    private final String id;
    private final String name;
    private final String location;

    public Warehouse(String id, String name, String location) {
        this.id = id;
        this.name = name;
        this.location = location;
    }

    public String getId() { return id; }
    public String getName() { return name; }
    public String getLocation() { return location; }

    @Override
    public String toString() {
        return name + " (" + id + ")";
    }
}
import threading
from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Product:
    sku: str
    name: str
    category: str
    price: float

    def __str__(self) -> str:
        return f"{self.name} [{self.sku}] ${self.price}"


@dataclass
class Warehouse:
    id: str
    name: str
    location: str

    def __str__(self) -> str:
        return f"{self.name} ({self.id})"
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <thread>
#include <stdexcept>
#include <algorithm>

struct Product {
    std::string sku;
    std::string name;
    std::string category;
    double price;
};

struct Warehouse {
    std::string id;
    std::string name;
    std::string location;
};
class Product {
    #sku; #name; #category; #price;

    constructor(sku, name, category, price) {
        this.#sku = sku;
        this.#name = name;
        this.#category = category;
        this.#price = price;
    }

    get sku() { return this.#sku; }
    get name() { return this.#name; }
    get category() { return this.#category; }
    get price() { return this.#price; }
    toString() { return `${this.#name} [${this.#sku}] $${this.#price}`; }
}

class Warehouse {
    #id; #name; #location;

    constructor(id, name, location) {
        this.#id = id;
        this.#name = name;
        this.#location = location;
    }

    get id() { return this.#id; }
    get name() { return this.#name; }
    get location() { return this.#location; }
}

InventoryItem

The stock record for a specific product at a specific warehouse. Each item has its own ReentrantLock (Java) / threading.Lock (Python) / std::mutex (C++) so concurrent operations on different products donโ€™t block each other.

๐Ÿ’ก Per-item locking is the key design choice here. A single global lock would serialize all operations, killing throughput. With per-item locks, two threads can safely mutate stock for different SKU+warehouse combinations in parallel.

class InventoryItem {
    private final String sku;
    private final String warehouseId;
    private int quantity;
    private final ReentrantLock lock = new ReentrantLock();

    public InventoryItem(String sku, String warehouseId, int quantity) {
        this.sku = sku;
        this.warehouseId = warehouseId;
        this.quantity = quantity;
    }

    public void addStock(int qty) {
        lock.lock();
        try {
            this.quantity += qty;
        } finally {
            lock.unlock();
        }
    }

    public void removeStock(int qty) {
        lock.lock();
        try {
            if (this.quantity < qty) {
                throw new InsufficientStockException(sku, warehouseId, qty, this.quantity);
            }
            this.quantity -= qty;
        } finally {
            lock.unlock();
        }
    }

    public int getQuantity() {
        lock.lock();
        try {
            return quantity;
        } finally {
            lock.unlock();
        }
    }

    public ReentrantLock getLock() { return lock; }
    public String getSku() { return sku; }
    public String getWarehouseId() { return warehouseId; }
}
class InventoryItem:
    def __init__(self, sku: str, warehouse_id: str, quantity: int = 0):
        self.sku = sku
        self.warehouse_id = warehouse_id
        self._quantity = quantity
        self.lock = threading.Lock()

    def add_stock(self, qty: int) -> None:
        with self.lock:
            self._quantity += qty

    def remove_stock(self, qty: int) -> None:
        with self.lock:
            if self._quantity < qty:
                raise InsufficientStockError(self.sku, self.warehouse_id, qty, self._quantity)
            self._quantity -= qty

    @property
    def quantity(self) -> int:
        with self.lock:
            return self._quantity
class InventoryItem {
private:
    std::string sku_;
    std::string warehouseId_;
    int quantity_;
    mutable std::mutex mtx_;

public:
    InventoryItem(const std::string& sku, const std::string& warehouseId, int quantity = 0)
        : sku_(sku), warehouseId_(warehouseId), quantity_(quantity) {}

    void addStock(int qty) {
        std::lock_guard<std::mutex> guard(mtx_);
        quantity_ += qty;
    }

    void removeStock(int qty) {
        std::lock_guard<std::mutex> guard(mtx_);
        if (quantity_ < qty) {
            throw InsufficientStockException(sku_, warehouseId_, qty, quantity_);
        }
        quantity_ -= qty;
    }

    int getQuantity() const {
        std::lock_guard<std::mutex> guard(mtx_);
        return quantity_;
    }

    std::mutex& getMutex() { return mtx_; }
    const std::string& getSku() const { return sku_; }
    const std::string& getWarehouseId() const { return warehouseId_; }
};
class InventoryItem {
    #sku; #warehouseId; #quantity;

    constructor(sku, warehouseId, quantity = 0) {
        this.#sku = sku;
        this.#warehouseId = warehouseId;
        this.#quantity = quantity;
    }

    addStock(qty) { this.#quantity += qty; }

    removeStock(qty) {
        if (this.#quantity < qty) {
            throw new InsufficientStockError(this.#sku, this.#warehouseId, qty, this.#quantity);
        }
        this.#quantity -= qty;
    }

    get quantity() { return this.#quantity; }
    get sku() { return this.#sku; }
    get warehouseId() { return this.#warehouseId; }
}

Custom Exceptions

Domain-specific exceptions for clear error semantics. Each exception carries the context needed for debugging โ€” which SKU, which warehouse, what was requested vs. available.

class InsufficientStockException extends RuntimeException {
    public InsufficientStockException(String sku, String warehouseId, int requested, int available) {
        super("Insufficient stock for SKU=" + sku + " at warehouse=" + warehouseId
              + ". Requested=" + requested + " Available=" + available);
    }
}

class ProductNotFoundException extends RuntimeException {
    public ProductNotFoundException(String sku) {
        super("Product not found: " + sku);
    }
}

class WarehouseNotFoundException extends RuntimeException {
    public WarehouseNotFoundException(String warehouseId) {
        super("Warehouse not found: " + warehouseId);
    }
}
class InsufficientStockError(Exception):
    def __init__(self, sku: str, warehouse_id: str, requested: int, available: int):
        super().__init__(
            f"Insufficient stock for SKU={sku} at warehouse={warehouse_id}. "
            f"Requested={requested} Available={available}"
        )


class ProductNotFoundError(Exception):
    def __init__(self, sku: str):
        super().__init__(f"Product not found: {sku}")


class WarehouseNotFoundError(Exception):
    def __init__(self, warehouse_id: str):
        super().__init__(f"Warehouse not found: {warehouse_id}")
class InsufficientStockException : public std::runtime_error {
public:
    InsufficientStockException(const std::string& sku, const std::string& warehouseId,
                               int requested, int available)
        : std::runtime_error("Insufficient stock for SKU=" + sku + " at warehouse=" + warehouseId
                             + ". Requested=" + std::to_string(requested)
                             + " Available=" + std::to_string(available)) {}
};

class ProductNotFoundException : public std::runtime_error {
public:
    ProductNotFoundException(const std::string& sku)
        : std::runtime_error("Product not found: " + sku) {}
};

class WarehouseNotFoundException : public std::runtime_error {
public:
    WarehouseNotFoundException(const std::string& warehouseId)
        : std::runtime_error("Warehouse not found: " + warehouseId) {}
};
class InsufficientStockError extends Error {
    constructor(sku, warehouseId, requested, available) {
        super(`Insufficient stock for SKU=${sku} at warehouse=${warehouseId}. ` +
              `Requested=${requested} Available=${available}`);
        this.name = 'InsufficientStockError';
    }
}

class ProductNotFoundError extends Error {
    constructor(sku) {
        super(`Product not found: ${sku}`);
        this.name = 'ProductNotFoundError';
    }
}

class WarehouseNotFoundError extends Error {
    constructor(warehouseId) {
        super(`Warehouse not found: ${warehouseId}`);
        this.name = 'WarehouseNotFoundError';
    }
}

Repository Interfaces and In-Memory Implementations

Abstractions for data access backed by ConcurrentHashMap (Java) or equivalent structures. The repository pattern means you can swap these for JDBC/Redis/DynamoDB implementations without touching business logic.

๐Ÿ’ก The Repository pattern is the single biggest win for testability here. Unit tests use these in-memory implementations; production uses a real database โ€” same service code, zero changes.

interface ProductRepository {
    void save(Product product);
    Product findBySku(String sku);
    boolean existsBySku(String sku);
}

interface WarehouseRepository {
    void save(Warehouse warehouse);
    Warehouse findById(String id);
    boolean existsById(String id);
}

interface InventoryRepository {
    void save(InventoryItem item);
    InventoryItem find(String sku, String warehouseId);
    List<InventoryItem> findAllBySku(String sku);
}

class InMemoryProductRepository implements ProductRepository {
    private final ConcurrentHashMap<String, Product> store = new ConcurrentHashMap<>();

    @Override
    public void save(Product product) { store.put(product.getSku(), product); }

    @Override
    public Product findBySku(String sku) { return store.get(sku); }

    @Override
    public boolean existsBySku(String sku) { return store.containsKey(sku); }
}

class InMemoryWarehouseRepository implements WarehouseRepository {
    private final ConcurrentHashMap<String, Warehouse> store = new ConcurrentHashMap<>();

    @Override
    public void save(Warehouse warehouse) { store.put(warehouse.getId(), warehouse); }

    @Override
    public Warehouse findById(String id) { return store.get(id); }

    @Override
    public boolean existsById(String id) { return store.containsKey(id); }
}

class InMemoryInventoryRepository implements InventoryRepository {
    private final ConcurrentHashMap<String, InventoryItem> store = new ConcurrentHashMap<>();

    private String key(String sku, String warehouseId) { return sku + "::" + warehouseId; }

    @Override
    public void save(InventoryItem item) {
        store.put(key(item.getSku(), item.getWarehouseId()), item);
    }

    @Override
    public InventoryItem find(String sku, String warehouseId) {
        return store.get(key(sku, warehouseId));
    }

    @Override
    public List<InventoryItem> findAllBySku(String sku) {
        List<InventoryItem> results = new ArrayList<>();
        for (InventoryItem item : store.values()) {
            if (item.getSku().equals(sku)) {
                results.add(item);
            }
        }
        return results;
    }
}
class ProductRepository:
    def __init__(self):
        self._store: dict[str, Product] = {}

    def save(self, product: Product) -> None:
        self._store[product.sku] = product

    def find_by_sku(self, sku: str) -> Optional[Product]:
        return self._store.get(sku)

    def exists_by_sku(self, sku: str) -> bool:
        return sku in self._store


class WarehouseRepository:
    def __init__(self):
        self._store: dict[str, Warehouse] = {}

    def save(self, warehouse: Warehouse) -> None:
        self._store[warehouse.id] = warehouse

    def find_by_id(self, wh_id: str) -> Optional[Warehouse]:
        return self._store.get(wh_id)

    def exists_by_id(self, wh_id: str) -> bool:
        return wh_id in self._store


class InventoryRepository:
    def __init__(self):
        self._store: dict[str, InventoryItem] = {}
        self._lock = threading.Lock()

    def _key(self, sku: str, warehouse_id: str) -> str:
        return f"{sku}::{warehouse_id}"

    def save(self, item: InventoryItem) -> None:
        with self._lock:
            self._store[self._key(item.sku, item.warehouse_id)] = item

    def find(self, sku: str, warehouse_id: str) -> Optional[InventoryItem]:
        return self._store.get(self._key(sku, warehouse_id))

    def find_all_by_sku(self, sku: str) -> list[InventoryItem]:
        return [item for item in self._store.values() if item.sku == sku]
// In C++, the InventoryService class holds all storage inline
// using std::unordered_map with std::unique_ptr for InventoryItems.
// See InventoryService section below.
class ProductRepository {
    #store = new Map();
    save(product) { this.#store.set(product.sku, product); }
    findBySku(sku) { return this.#store.get(sku) || null; }
    existsBySku(sku) { return this.#store.has(sku); }
}

class WarehouseRepository {
    #store = new Map();
    save(warehouse) { this.#store.set(warehouse.id, warehouse); }
    findById(id) { return this.#store.get(id) || null; }
    existsById(id) { return this.#store.has(id); }
}

class InventoryRepository {
    #store = new Map();
    #key(sku, warehouseId) { return `${sku}::${warehouseId}`; }
    save(item) { this.#store.set(this.#key(item.sku, item.warehouseId), item); }
    find(sku, warehouseId) { return this.#store.get(this.#key(sku, warehouseId)) || null; }
    findAllBySku(sku) {
        return [...this.#store.values()].filter(item => item.sku === sku);
    }
}

InventoryService

The service layer that orchestrates all stock operations. It validates inputs, coordinates between repositories, and handles thread-safety for transfers using lock ordering.

๐Ÿ’ก Lock ordering is critical for transferStock. When transferring Aโ†’B, we always lock the item with the lexicographically smaller warehouse ID first. This prevents deadlocks when two threads simultaneously try Aโ†’B and Bโ†’A โ€” both will lock in the same order.

class InventoryService {
    private final ProductRepository productRepo;
    private final WarehouseRepository warehouseRepo;
    private final InventoryRepository inventoryRepo;

    public InventoryService(ProductRepository productRepo, WarehouseRepository warehouseRepo,
                            InventoryRepository inventoryRepo) {
        this.productRepo = productRepo;
        this.warehouseRepo = warehouseRepo;
        this.inventoryRepo = inventoryRepo;
    }

    public void addProduct(String sku, String name, String category, double price) {
        if (productRepo.existsBySku(sku)) {
            throw new IllegalArgumentException("Product already exists: " + sku);
        }
        productRepo.save(new Product(sku, name, category, price));
    }

    public void addWarehouse(String id, String name, String location) {
        if (warehouseRepo.existsById(id)) {
            throw new IllegalArgumentException("Warehouse already exists: " + id);
        }
        warehouseRepo.save(new Warehouse(id, name, location));
    }

    public void addStock(String sku, String warehouseId, int quantity) {
        validateProductExists(sku);
        validateWarehouseExists(warehouseId);
        if (quantity <= 0) throw new IllegalArgumentException("Quantity must be positive");

        InventoryItem item = inventoryRepo.find(sku, warehouseId);
        if (item == null) {
            item = new InventoryItem(sku, warehouseId, 0);
            inventoryRepo.save(item);
        }
        item.addStock(quantity);
    }

    public void removeStock(String sku, String warehouseId, int quantity) {
        validateProductExists(sku);
        validateWarehouseExists(warehouseId);
        if (quantity <= 0) throw new IllegalArgumentException("Quantity must be positive");

        InventoryItem item = inventoryRepo.find(sku, warehouseId);
        if (item == null) {
            throw new InsufficientStockException(sku, warehouseId, quantity, 0);
        }
        item.removeStock(quantity);
    }

    public int getStock(String sku) {
        validateProductExists(sku);
        List<InventoryItem> items = inventoryRepo.findAllBySku(sku);
        return items.stream().mapToInt(InventoryItem::getQuantity).sum();
    }

    public int getStockByWarehouse(String sku, String warehouseId) {
        validateProductExists(sku);
        validateWarehouseExists(warehouseId);
        InventoryItem item = inventoryRepo.find(sku, warehouseId);
        return item == null ? 0 : item.getQuantity();
    }

    public void transferStock(String sku, String fromWarehouse, String toWarehouse, int quantity) {
        validateProductExists(sku);
        validateWarehouseExists(fromWarehouse);
        validateWarehouseExists(toWarehouse);
        if (quantity <= 0) throw new IllegalArgumentException("Quantity must be positive");
        if (fromWarehouse.equals(toWarehouse)) {
            throw new IllegalArgumentException("Source and destination warehouse must differ");
        }

        InventoryItem source = inventoryRepo.find(sku, fromWarehouse);
        if (source == null) {
            throw new InsufficientStockException(sku, fromWarehouse, quantity, 0);
        }

        InventoryItem dest = inventoryRepo.find(sku, toWarehouse);
        if (dest == null) {
            dest = new InventoryItem(sku, toWarehouse, 0);
            inventoryRepo.save(dest);
        }

        // Lock ordering to prevent deadlock: lock by natural order of warehouseId
        InventoryItem first = fromWarehouse.compareTo(toWarehouse) < 0 ? source : dest;
        InventoryItem second = fromWarehouse.compareTo(toWarehouse) < 0 ? dest : source;

        first.getLock().lock();
        try {
            second.getLock().lock();
            try {
                if (source.getQuantity() < quantity) {
                    throw new InsufficientStockException(sku, fromWarehouse, quantity, source.getQuantity());
                }
                source.removeStock(quantity);
                dest.addStock(quantity);
            } finally {
                second.getLock().unlock();
            }
        } finally {
            first.getLock().unlock();
        }
    }

    private void validateProductExists(String sku) {
        if (!productRepo.existsBySku(sku)) {
            throw new ProductNotFoundException(sku);
        }
    }

    private void validateWarehouseExists(String warehouseId) {
        if (!warehouseRepo.existsById(warehouseId)) {
            throw new WarehouseNotFoundException(warehouseId);
        }
    }
}
class InventoryService:
    def __init__(self, product_repo: ProductRepository, warehouse_repo: WarehouseRepository,
                 inventory_repo: InventoryRepository):
        self._product_repo = product_repo
        self._warehouse_repo = warehouse_repo
        self._inventory_repo = inventory_repo

    def add_product(self, sku: str, name: str, category: str, price: float) -> None:
        if self._product_repo.exists_by_sku(sku):
            raise ValueError(f"Product already exists: {sku}")
        self._product_repo.save(Product(sku, name, category, price))

    def add_warehouse(self, wh_id: str, name: str, location: str) -> None:
        if self._warehouse_repo.exists_by_id(wh_id):
            raise ValueError(f"Warehouse already exists: {wh_id}")
        self._warehouse_repo.save(Warehouse(wh_id, name, location))

    def add_stock(self, sku: str, warehouse_id: str, quantity: int) -> None:
        self._validate_product(sku)
        self._validate_warehouse(warehouse_id)
        if quantity <= 0:
            raise ValueError("Quantity must be positive")

        item = self._inventory_repo.find(sku, warehouse_id)
        if item is None:
            item = InventoryItem(sku, warehouse_id, 0)
            self._inventory_repo.save(item)
        item.add_stock(quantity)

    def remove_stock(self, sku: str, warehouse_id: str, quantity: int) -> None:
        self._validate_product(sku)
        self._validate_warehouse(warehouse_id)
        if quantity <= 0:
            raise ValueError("Quantity must be positive")

        item = self._inventory_repo.find(sku, warehouse_id)
        if item is None:
            raise InsufficientStockError(sku, warehouse_id, quantity, 0)
        item.remove_stock(quantity)

    def get_stock(self, sku: str) -> int:
        self._validate_product(sku)
        items = self._inventory_repo.find_all_by_sku(sku)
        return sum(item.quantity for item in items)

    def get_stock_by_warehouse(self, sku: str, warehouse_id: str) -> int:
        self._validate_product(sku)
        self._validate_warehouse(warehouse_id)
        item = self._inventory_repo.find(sku, warehouse_id)
        return item.quantity if item else 0

    def transfer_stock(self, sku: str, from_wh: str, to_wh: str, quantity: int) -> None:
        self._validate_product(sku)
        self._validate_warehouse(from_wh)
        self._validate_warehouse(to_wh)
        if quantity <= 0:
            raise ValueError("Quantity must be positive")
        if from_wh == to_wh:
            raise ValueError("Source and destination warehouse must differ")

        source = self._inventory_repo.find(sku, from_wh)
        if source is None:
            raise InsufficientStockError(sku, from_wh, quantity, 0)

        dest = self._inventory_repo.find(sku, to_wh)
        if dest is None:
            dest = InventoryItem(sku, to_wh, 0)
            self._inventory_repo.save(dest)

        # Lock ordering to prevent deadlock
        first, second = (source, dest) if from_wh < to_wh else (dest, source)
        with first.lock:
            with second.lock:
                if source.quantity < quantity:
                    raise InsufficientStockError(sku, from_wh, quantity, source.quantity)
                source.remove_stock(quantity)
                dest.add_stock(quantity)

    def _validate_product(self, sku: str) -> None:
        if not self._product_repo.exists_by_sku(sku):
            raise ProductNotFoundError(sku)

    def _validate_warehouse(self, warehouse_id: str) -> None:
        if not self._warehouse_repo.exists_by_id(warehouse_id):
            raise WarehouseNotFoundError(warehouse_id)
class InventoryService {
private:
    std::unordered_map<std::string, Product> products_;
    std::unordered_map<std::string, Warehouse> warehouses_;
    std::unordered_map<std::string, std::unique_ptr<InventoryItem>> inventory_;
    std::mutex repoMtx_;

    std::string key(const std::string& sku, const std::string& warehouseId) {
        return sku + "::" + warehouseId;
    }

    void validateProduct(const std::string& sku) {
        if (products_.find(sku) == products_.end()) {
            throw ProductNotFoundException(sku);
        }
    }

    void validateWarehouse(const std::string& warehouseId) {
        if (warehouses_.find(warehouseId) == warehouses_.end()) {
            throw WarehouseNotFoundException(warehouseId);
        }
    }

public:
    void addProduct(const std::string& sku, const std::string& name,
                    const std::string& category, double price) {
        std::lock_guard<std::mutex> guard(repoMtx_);
        if (products_.count(sku)) throw std::invalid_argument("Product already exists: " + sku);
        products_[sku] = {sku, name, category, price};
    }

    void addWarehouse(const std::string& id, const std::string& name, const std::string& loc) {
        std::lock_guard<std::mutex> guard(repoMtx_);
        if (warehouses_.count(id)) throw std::invalid_argument("Warehouse already exists: " + id);
        warehouses_[id] = {id, name, loc};
    }

    void addStock(const std::string& sku, const std::string& warehouseId, int quantity) {
        validateProduct(sku);
        validateWarehouse(warehouseId);
        if (quantity <= 0) throw std::invalid_argument("Quantity must be positive");

        std::lock_guard<std::mutex> guard(repoMtx_);
        auto k = key(sku, warehouseId);
        if (inventory_.find(k) == inventory_.end()) {
            inventory_[k] = std::make_unique<InventoryItem>(sku, warehouseId, 0);
        }
        inventory_[k]->addStock(quantity);
    }

    void removeStock(const std::string& sku, const std::string& warehouseId, int quantity) {
        validateProduct(sku);
        validateWarehouse(warehouseId);
        if (quantity <= 0) throw std::invalid_argument("Quantity must be positive");

        auto k = key(sku, warehouseId);
        if (inventory_.find(k) == inventory_.end()) {
            throw InsufficientStockException(sku, warehouseId, quantity, 0);
        }
        inventory_[k]->removeStock(quantity);
    }

    int getStock(const std::string& sku) {
        validateProduct(sku);
        int total = 0;
        for (auto& [k, item] : inventory_) {
            if (item->getSku() == sku) total += item->getQuantity();
        }
        return total;
    }

    int getStockByWarehouse(const std::string& sku, const std::string& warehouseId) {
        validateProduct(sku);
        validateWarehouse(warehouseId);
        auto k = key(sku, warehouseId);
        if (inventory_.find(k) == inventory_.end()) return 0;
        return inventory_[k]->getQuantity();
    }

    void transferStock(const std::string& sku, const std::string& fromWh,
                       const std::string& toWh, int quantity) {
        validateProduct(sku);
        validateWarehouse(fromWh);
        validateWarehouse(toWh);
        if (quantity <= 0) throw std::invalid_argument("Quantity must be positive");
        if (fromWh == toWh) throw std::invalid_argument("Source and destination must differ");

        auto srcKey = key(sku, fromWh);
        auto dstKey = key(sku, toWh);

        if (inventory_.find(srcKey) == inventory_.end()) {
            throw InsufficientStockException(sku, fromWh, quantity, 0);
        }
        {
            std::lock_guard<std::mutex> guard(repoMtx_);
            if (inventory_.find(dstKey) == inventory_.end()) {
                inventory_[dstKey] = std::make_unique<InventoryItem>(sku, toWh, 0);
            }
        }

        auto* source = inventory_[srcKey].get();
        auto* dest = inventory_[dstKey].get();

        // Lock ordering to prevent deadlock
        auto* first = (fromWh < toWh) ? source : dest;
        auto* second = (fromWh < toWh) ? dest : source;

        std::lock_guard<std::mutex> lock1(first->getMutex());
        std::lock_guard<std::mutex> lock2(second->getMutex());

        if (source->getQuantity() < quantity) {
            throw InsufficientStockException(sku, fromWh, quantity, source->getQuantity());
        }
        source->removeStock(quantity);
        dest->addStock(quantity);
    }
};
// Note: JS is single-threaded so stock operations are inherently atomic
// within a single event loop tick.

class InventoryService {
    #productRepo; #warehouseRepo; #inventoryRepo;

    constructor(productRepo, warehouseRepo, inventoryRepo) {
        this.#productRepo = productRepo;
        this.#warehouseRepo = warehouseRepo;
        this.#inventoryRepo = inventoryRepo;
    }

    addProduct(sku, name, category, price) {
        if (this.#productRepo.existsBySku(sku)) {
            throw new Error(`Product already exists: ${sku}`);
        }
        this.#productRepo.save(new Product(sku, name, category, price));
    }

    addWarehouse(id, name, location) {
        if (this.#warehouseRepo.existsById(id)) {
            throw new Error(`Warehouse already exists: ${id}`);
        }
        this.#warehouseRepo.save(new Warehouse(id, name, location));
    }

    addStock(sku, warehouseId, quantity) {
        this.#validateProduct(sku);
        this.#validateWarehouse(warehouseId);
        if (quantity <= 0) throw new Error('Quantity must be positive');

        let item = this.#inventoryRepo.find(sku, warehouseId);
        if (!item) {
            item = new InventoryItem(sku, warehouseId, 0);
            this.#inventoryRepo.save(item);
        }
        item.addStock(quantity);
    }

    removeStock(sku, warehouseId, quantity) {
        this.#validateProduct(sku);
        this.#validateWarehouse(warehouseId);
        if (quantity <= 0) throw new Error('Quantity must be positive');

        const item = this.#inventoryRepo.find(sku, warehouseId);
        if (!item) throw new InsufficientStockError(sku, warehouseId, quantity, 0);
        item.removeStock(quantity);
    }

    getStock(sku) {
        this.#validateProduct(sku);
        const items = this.#inventoryRepo.findAllBySku(sku);
        return items.reduce((sum, item) => sum + item.quantity, 0);
    }

    getStockByWarehouse(sku, warehouseId) {
        this.#validateProduct(sku);
        this.#validateWarehouse(warehouseId);
        const item = this.#inventoryRepo.find(sku, warehouseId);
        return item ? item.quantity : 0;
    }

    transferStock(sku, fromWarehouse, toWarehouse, quantity) {
        this.#validateProduct(sku);
        this.#validateWarehouse(fromWarehouse);
        this.#validateWarehouse(toWarehouse);
        if (quantity <= 0) throw new Error('Quantity must be positive');
        if (fromWarehouse === toWarehouse) {
            throw new Error('Source and destination warehouse must differ');
        }

        const source = this.#inventoryRepo.find(sku, fromWarehouse);
        if (!source) throw new InsufficientStockError(sku, fromWarehouse, quantity, 0);

        let dest = this.#inventoryRepo.find(sku, toWarehouse);
        if (!dest) {
            dest = new InventoryItem(sku, toWarehouse, 0);
            this.#inventoryRepo.save(dest);
        }

        // In single-threaded JS this is atomic within one tick
        source.removeStock(quantity);
        dest.addStock(quantity);
    }

    #validateProduct(sku) {
        if (!this.#productRepo.existsBySku(sku)) throw new ProductNotFoundError(sku);
    }

    #validateWarehouse(warehouseId) {
        if (!this.#warehouseRepo.existsById(warehouseId)) throw new WarehouseNotFoundError(warehouseId);
    }
}

Main / Demo

End-to-end demonstration: sets up warehouses and products, performs stock operations, executes an atomic transfer, tests error handling, and runs a concurrent stress test to verify thread-safety.

public class InventoryManagementDemo {
    public static void main(String[] args) {
        ProductRepository productRepo = new InMemoryProductRepository();
        WarehouseRepository warehouseRepo = new InMemoryWarehouseRepository();
        InventoryRepository inventoryRepo = new InMemoryInventoryRepository();
        InventoryService service = new InventoryService(productRepo, warehouseRepo, inventoryRepo);

        // Setup
        service.addWarehouse("WH-1", "Mumbai Warehouse", "Mumbai");
        service.addWarehouse("WH-2", "Delhi Warehouse", "Delhi");
        service.addProduct("SKU-001", "Wireless Mouse", "Electronics", 29.99);
        service.addProduct("SKU-002", "Mechanical Keyboard", "Electronics", 79.99);

        // Stock operations
        service.addStock("SKU-001", "WH-1", 100);
        service.addStock("SKU-001", "WH-2", 50);
        service.addStock("SKU-002", "WH-1", 30);

        System.out.println("=== Inventory Status ===");
        System.out.println("SKU-001 total stock: " + service.getStock("SKU-001"));
        System.out.println("SKU-001 at WH-1: " + service.getStockByWarehouse("SKU-001", "WH-1"));
        System.out.println("SKU-001 at WH-2: " + service.getStockByWarehouse("SKU-001", "WH-2"));

        // Transfer stock
        System.out.println("\n=== Transfer 20 units of SKU-001 from WH-1 to WH-2 ===");
        service.transferStock("SKU-001", "WH-1", "WH-2", 20);
        System.out.println("SKU-001 at WH-1: " + service.getStockByWarehouse("SKU-001", "WH-1"));
        System.out.println("SKU-001 at WH-2: " + service.getStockByWarehouse("SKU-001", "WH-2"));
        System.out.println("SKU-001 total: " + service.getStock("SKU-001"));

        // Remove stock
        System.out.println("\n=== Remove 10 units of SKU-002 from WH-1 ===");
        service.removeStock("SKU-002", "WH-1", 10);
        System.out.println("SKU-002 at WH-1: " + service.getStockByWarehouse("SKU-002", "WH-1"));

        // Error handling
        System.out.println("\n=== Error Cases ===");
        try {
            service.removeStock("SKU-002", "WH-1", 999);
        } catch (InsufficientStockException e) {
            System.out.println("Caught: " + e.getMessage());
        }
        try {
            service.getStock("SKU-INVALID");
        } catch (ProductNotFoundException e) {
            System.out.println("Caught: " + e.getMessage());
        }

        // Concurrent stress test
        System.out.println("\n=== Concurrent Stress Test ===");
        ExecutorService executor = Executors.newFixedThreadPool(10);
        List<Future<?>> futures = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            futures.add(executor.submit(() -> service.addStock("SKU-001", "WH-1", 1)));
        }
        for (Future<?> f : futures) {
            try { f.get(); } catch (Exception ignored) {}
        }
        executor.shutdown();
        System.out.println("After 100 concurrent +1 adds, SKU-001 at WH-1: "
                           + service.getStockByWarehouse("SKU-001", "WH-1"));
        System.out.println("Expected: 180 (80 initial + 100 concurrent adds)");
    }
}
def main():
    product_repo = ProductRepository()
    warehouse_repo = WarehouseRepository()
    inventory_repo = InventoryRepository()
    service = InventoryService(product_repo, warehouse_repo, inventory_repo)

    # Setup
    service.add_warehouse("WH-1", "Mumbai Warehouse", "Mumbai")
    service.add_warehouse("WH-2", "Delhi Warehouse", "Delhi")
    service.add_product("SKU-001", "Wireless Mouse", "Electronics", 29.99)
    service.add_product("SKU-002", "Mechanical Keyboard", "Electronics", 79.99)

    # Stock operations
    service.add_stock("SKU-001", "WH-1", 100)
    service.add_stock("SKU-001", "WH-2", 50)
    service.add_stock("SKU-002", "WH-1", 30)

    print("=== Inventory Status ===")
    print(f"SKU-001 total stock: {service.get_stock('SKU-001')}")
    print(f"SKU-001 at WH-1: {service.get_stock_by_warehouse('SKU-001', 'WH-1')}")
    print(f"SKU-001 at WH-2: {service.get_stock_by_warehouse('SKU-001', 'WH-2')}")

    # Transfer
    print("\n=== Transfer 20 units of SKU-001 from WH-1 to WH-2 ===")
    service.transfer_stock("SKU-001", "WH-1", "WH-2", 20)
    print(f"SKU-001 at WH-1: {service.get_stock_by_warehouse('SKU-001', 'WH-1')}")
    print(f"SKU-001 at WH-2: {service.get_stock_by_warehouse('SKU-001', 'WH-2')}")
    print(f"SKU-001 total: {service.get_stock('SKU-001')}")

    # Remove stock
    print("\n=== Remove 10 units of SKU-002 from WH-1 ===")
    service.remove_stock("SKU-002", "WH-1", 10)
    print(f"SKU-002 at WH-1: {service.get_stock_by_warehouse('SKU-002', 'WH-1')}")

    # Error handling
    print("\n=== Error Cases ===")
    try:
        service.remove_stock("SKU-002", "WH-1", 999)
    except InsufficientStockError as e:
        print(f"Caught: {e}")
    try:
        service.get_stock("SKU-INVALID")
    except ProductNotFoundError as e:
        print(f"Caught: {e}")

    # Concurrent stress test
    print("\n=== Concurrent Stress Test ===")
    threads = []
    for _ in range(100):
        t = threading.Thread(target=service.add_stock, args=("SKU-001", "WH-1", 1))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

    print(f"After 100 concurrent +1 adds, SKU-001 at WH-1: "
          f"{service.get_stock_by_warehouse('SKU-001', 'WH-1')}")
    print("Expected: 180 (80 initial + 100 concurrent adds)")


if __name__ == "__main__":
    main()
int main() {
    InventoryService service;

    service.addWarehouse("WH-1", "Mumbai Warehouse", "Mumbai");
    service.addWarehouse("WH-2", "Delhi Warehouse", "Delhi");
    service.addProduct("SKU-001", "Wireless Mouse", "Electronics", 29.99);
    service.addProduct("SKU-002", "Mechanical Keyboard", "Electronics", 79.99);

    service.addStock("SKU-001", "WH-1", 100);
    service.addStock("SKU-001", "WH-2", 50);
    service.addStock("SKU-002", "WH-1", 30);

    std::cout << "=== Inventory Status ===" << std::endl;
    std::cout << "SKU-001 total stock: " << service.getStock("SKU-001") << std::endl;
    std::cout << "SKU-001 at WH-1: " << service.getStockByWarehouse("SKU-001", "WH-1") << std::endl;
    std::cout << "SKU-001 at WH-2: " << service.getStockByWarehouse("SKU-001", "WH-2") << std::endl;

    std::cout << "\n=== Transfer 20 units from WH-1 to WH-2 ===" << std::endl;
    service.transferStock("SKU-001", "WH-1", "WH-2", 20);
    std::cout << "SKU-001 at WH-1: " << service.getStockByWarehouse("SKU-001", "WH-1") << std::endl;
    std::cout << "SKU-001 at WH-2: " << service.getStockByWarehouse("SKU-001", "WH-2") << std::endl;

    std::cout << "\n=== Remove 10 units of SKU-002 ===" << std::endl;
    service.removeStock("SKU-002", "WH-1", 10);
    std::cout << "SKU-002 at WH-1: " << service.getStockByWarehouse("SKU-002", "WH-1") << std::endl;

    std::cout << "\n=== Error Cases ===" << std::endl;
    try {
        service.removeStock("SKU-002", "WH-1", 999);
    } catch (const InsufficientStockException& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }
    try {
        service.getStock("SKU-INVALID");
    } catch (const ProductNotFoundException& e) {
        std::cout << "Caught: " << e.what() << std::endl;
    }

    // Concurrent stress test
    std::cout << "\n=== Concurrent Stress Test ===" << std::endl;
    std::vector<std::thread> threads;
    for (int i = 0; i < 100; ++i) {
        threads.emplace_back([&service]() {
            service.addStock("SKU-001", "WH-1", 1);
        });
    }
    for (auto& t : threads) t.join();

    std::cout << "After 100 concurrent +1 adds, SKU-001 at WH-1: "
              << service.getStockByWarehouse("SKU-001", "WH-1") << std::endl;
    std::cout << "Expected: 180 (80 initial + 100 concurrent adds)" << std::endl;

    return 0;
}
function main() {
    const productRepo = new ProductRepository();
    const warehouseRepo = new WarehouseRepository();
    const inventoryRepo = new InventoryRepository();
    const service = new InventoryService(productRepo, warehouseRepo, inventoryRepo);

    service.addWarehouse('WH-1', 'Mumbai Warehouse', 'Mumbai');
    service.addWarehouse('WH-2', 'Delhi Warehouse', 'Delhi');
    service.addProduct('SKU-001', 'Wireless Mouse', 'Electronics', 29.99);
    service.addProduct('SKU-002', 'Mechanical Keyboard', 'Electronics', 79.99);

    service.addStock('SKU-001', 'WH-1', 100);
    service.addStock('SKU-001', 'WH-2', 50);
    service.addStock('SKU-002', 'WH-1', 30);

    console.log('=== Inventory Status ===');
    console.log(`SKU-001 total stock: ${service.getStock('SKU-001')}`);
    console.log(`SKU-001 at WH-1: ${service.getStockByWarehouse('SKU-001', 'WH-1')}`);
    console.log(`SKU-001 at WH-2: ${service.getStockByWarehouse('SKU-001', 'WH-2')}`);

    console.log('\n=== Transfer 20 units of SKU-001 from WH-1 to WH-2 ===');
    service.transferStock('SKU-001', 'WH-1', 'WH-2', 20);
    console.log(`SKU-001 at WH-1: ${service.getStockByWarehouse('SKU-001', 'WH-1')}`);
    console.log(`SKU-001 at WH-2: ${service.getStockByWarehouse('SKU-001', 'WH-2')}`);
    console.log(`SKU-001 total: ${service.getStock('SKU-001')}`);

    console.log('\n=== Remove 10 units of SKU-002 from WH-1 ===');
    service.removeStock('SKU-002', 'WH-1', 10);
    console.log(`SKU-002 at WH-1: ${service.getStockByWarehouse('SKU-002', 'WH-1')}`);

    console.log('\n=== Error Cases ===');
    try {
        service.removeStock('SKU-002', 'WH-1', 999);
    } catch (e) {
        console.log(`Caught: ${e.message}`);
    }
    try {
        service.getStock('SKU-INVALID');
    } catch (e) {
        console.log(`Caught: ${e.message}`);
    }

    console.log('\n=== Simulated Concurrent Adds (sequential in JS) ===');
    for (let i = 0; i < 100; i++) {
        service.addStock('SKU-001', 'WH-1', 1);
    }
    console.log(`After 100 sequential +1 adds, SKU-001 at WH-1: ${service.getStockByWarehouse('SKU-001', 'WH-1')}`);
    console.log('Expected: 180 (80 initial + 100 adds)');
}

main();

Follow-up Questions

  1. How would you handle distributed warehouses across regions? Consider eventual consistency, distributed locks (Redlock), and conflict resolution for concurrent stock updates across data centers.

  2. How would you implement event sourcing for audit trails? Instead of storing current state, store all stock change events (StockAdded, StockRemoved, StockTransferred) and rebuild current state by replaying events. This gives complete audit history.

  3. How would you implement low-stock alerts? Observer pattern - InventoryItem notifies a listener when quantity drops below a configurable threshold. The listener could trigger notifications, auto-reorder, or dashboard updates.

  4. How would you handle bulk stock imports (CSV with 100K rows)? Batch processing with a queue. Validate all rows first, then apply changes in a single transaction. Show progress and handle partial failures gracefully.

  5. How would you add support for stock reservations (e.g., items in cart but not yet purchased)? Add a reservedQuantity field to InventoryItem. Available = quantity - reserved. Reserve on add-to-cart, release on timeout or checkout.

  6. How would you implement stock reconciliation between physical counts and system records? Scheduled reconciliation job that compares physical count entries against system state, generates discrepancy reports, and supports adjustment workflows with approval.

  7. How would you handle the thundering herd problem during flash sales? Rate limiting at the service layer, optimistic locking with retry, or a queue-based approach where stock deductions are serialized through a single-threaded consumer.



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