Compile Ready
All low level design problems
Low Level Design/Intermediate/Real-world Systems

Design an Inventory Management System

SKUs, warehouses, stock movements, and reorder rules with an event-driven low-stock notifier.

Intermediate 50m interview 16m read Medium frequency Popularity 74
Observer Strategy Repository State Amazon Flipkart Swiggy

Problem Statement

Design the object model for an Inventory Management System that tracks products across warehouses, records on-hand and reserved quantities, and protects stock while orders are being checked out.

The core interview challenge is the correctness boundary: two order threads must not both decrement the same available units. The design should also send low-stock notifications exactly when a product crosses its threshold and keep replenishment policy pluggable instead of hard-coded.

Business context

Inventory systems sit behind shopping carts, food delivery, warehouses, and marketplaces. A small race can oversell a SKU, break fulfillment promises, and create expensive support issues. Interviewers use this problem to evaluate whether you can model domain invariants, isolate persistence with a Repository, apply Observer for threshold events, and use Strategy for reorder decisions.

Functional Requirements

  • Register products with SKU, name, and a low-stock threshold.

  • Register warehouses and keep stock independently per SKU and warehouse.

  • Add physical stock to a warehouse after receiving inventory.

  • Reserve available stock for an order without overselling.

  • Release previously reserved stock when an order is cancelled or payment fails.

  • Ship reserved stock by decrementing on-hand inventory after fulfillment.

  • Notify observers when available stock crosses below the product threshold.

  • Generate replenishment recommendations through a pluggable strategy.

Non-Functional Requirements

Concurrency safety

The check of available quantity and the reserved or shipped mutation must happen under one lock per stock item.

Extensibility

New reorder policies, alert sinks, and repository implementations should be additive classes, not edits to reservation flow.

Consistency of invariants

reserved must never exceed onHand, and available = onHand - reserved must never be negative.

Low latency hot path

Reserve and release should be O(1) for a known SKU and warehouse, with synchronization scoped to that stock item.

Auditable operations

Production implementations should attach order ids, actor ids, and timestamps to stock movements for reconciliation.

Requirement Clarification

QIs stock tracked globally or per warehouse?

Track stock per SKU + warehouse. A product can be available in one warehouse and low in another, so the stock item is the aggregate for that pair.

QWhat is the difference between reserve, release, and ship?

Reserve moves units from available to reserved for an order. Release returns reserved units to available. Ship consumes reserved units by decrementing physical on-hand stock.

QWhen should low-stock observers be notified?

Notify when available stock crosses from at or above the threshold to below it. This prevents repeated alerts for every later operation while the SKU is already low.

QDo we need purchase orders or supplier integration?

Not in the base design. The system returns a ReorderRecommendation from a strategy; a supplier adapter can consume that recommendation later.

QIs persistence required for the interview implementation?

Use an in-memory Repository for clarity, but keep the service dependent on the Repository interface so SQL, Redis, or event-sourced storage can replace it.

UML Class Diagram

Rendering diagram…
The service is the facade and Singleton entry point. It delegates persistence to the Repository, stock invariants to **StockItem**, alert delivery to observers, and reorder quantity choice to a Strategy.

Sequence Diagram

Rendering diagram…
Reservation is the critical path: the item lock makes check-then-reserve atomic, and the service raises Observer notifications only after it has a before and after snapshot.

Entity Identification

Product

Immutable SKU metadata: identifier, display name, and the low-stock threshold used by alerting and replenishment.

skunamelowStockThreshold

Warehouse

Immutable location metadata. It scopes inventory so the same SKU can have separate availability in different facilities.

idname

StockItem

Aggregate for one Product in one Warehouse. Owns onHand, reserved, and the lock that protects every mutation.

productwarehouseonHandreservedlock

InventoryService

Application facade. Validates requests, loads stock items from the Repository, coordinates reserve, release, and ship operations, and triggers low-stock observers.

repositoryreplenishmentStrategyobservers

InventoryRepository

Persistence seam for products, warehouses, and stock items. The sample uses an in-memory implementation; production can replace it with durable storage.

productswarehousesstockItems

ReplenishmentStrategy

Policy interface that decides whether and how many units to reorder when stock becomes low.

recommend(snapshot)

InventoryObserver

Observer interface for low-stock events. Email, Slack, purchase-order creation, and metrics can subscribe independently.

onLowStock(snapshot, recommendation)

Design Patterns Used

Observer

InventoryObserver decouples stock changes from alert delivery. The service publishes a low-stock event and does not care whether the subscriber sends email, updates metrics, or creates a purchase order.

Strategy

ReplenishmentStrategy keeps reorder logic pluggable. Fixed quantity, top-up-to-target, supplier lead-time, and demand-forecast policies all implement the same method.

Repository

InventoryRepository hides storage details from the service. Reservation logic depends on an interface while the demo uses InMemoryInventoryRepository.

Singleton

InventoryService.getInstance gives small applications one shared inventory facade. The design still exposes an isolated factory for tests and demos that need fresh state.

Step-by-Step Design

  1. 1Model stock at SKU and warehouse granularity

    Use StockItem as the aggregate root for one product in one warehouse. Global product state is not enough because reservations and thresholds are location-specific.

  2. 2Represent availability as a derived value

    Store onHand and reserved; compute available stock instead of storing it separately. That removes a third value that can drift out of sync.

    public int getAvailable() {
        return onHand - reserved;
    }
  3. 3Make reserve an atomic check-then-update

    The race is between checking available units and increasing reserved units. Put both operations under the same stock-item lock.

    lock.lock();
    try {
        if (availableLocked() < quantity) {
            return ReservationResult.rejected(snapshotLocked(), "Not enough stock");
        }
        reserved += quantity;
        return ReservationResult.accepted(before, snapshotLocked());
    } finally {
        lock.unlock();
    }
  4. 4Separate reserve, release, and ship

    Reserve protects stock for checkout, release cancels the hold, and ship decrements physical inventory after fulfillment. Keeping these verbs separate prevents order-state ambiguity.

  5. 5Notify only on threshold crossing

    Compare before and after snapshots. Observers should hear about a transition into low stock, not every operation while already low.

    if (before.getAvailable() >= after.getLowStockThreshold()
            && after.getAvailable() < after.getLowStockThreshold()) {
        ReorderRecommendation recommendation = replenishmentStrategy.recommend(after);
        for (InventoryObserver observer : observers) {
            observer.onLowStock(after, recommendation);
        }
    }
  6. 6Keep policies and storage behind interfaces

    The service depends on InventoryRepository, InventoryObserver, and ReplenishmentStrategy. This keeps persistence, notifications, and reorder math replaceable.

Complete Java Implementation

Loading…

Explanation of Every Class

Product

Immutable SKU definition. Equality is based on sku, and lowStockThreshold drives alerting and replenishment decisions.

Warehouse

Immutable facility definition. Keeping warehouse identity separate from product identity lets the same SKU have independent stock in each location.

StockItem

The concurrency-safe aggregate. It owns onHand, reserved, StockSnapshot, StockChange, and ReservationResult; reserve, release, and ship all hold the same ReentrantLock while checking and mutating state.

ReplenishmentStrategy

The reorder policy seam. The file includes fixed-quantity and top-up-to-target strategies plus ReorderRecommendation, so the service can ask for a recommendation without knowing the formula.

InventoryObserver

Observer contract for threshold breaches. The console observer is deliberately simple, but email, Slack, metrics, or purchase-order adapters would implement the same method.

InventoryService

Singleton facade and orchestration layer. It registers catalog data, loads stock through InventoryRepository, calls StockItem for atomic mutations, and notifies observers after threshold crossings.

Main

Executable demo. Two threads each try to reserve six units from ten on-hand units; the stock-item lock allows one reservation and cleanly rejects the other, while the winner triggers a low-stock notification.

Dry Run

Sample input

SKU BOOK-001 in warehouse WH-BLR has onHand 10, reserved 0, and low-stock threshold 5. Two order threads each try to reserve 6 units.

StepORDER-1ORDER-2onHandreservedOutcome
1starts reserve(6)starts reserve(6)100Both requests target the same stock item
2acquires item lockwaits for item lock100ORDER-1 checks available = 10
3reserved becomes 6still waiting106Available drops to 4, crossing threshold 5
4observer notifiedacquires item lock106Strategy recommends reorder before ORDER-2 checks
5ships reserved 6reserve(6) rejected40Only 4 units remain, so oversell is prevented

The important transition is Step 3: available stock moves from 10 to 4 inside the lock, so the second order can only see the committed result and the observer fires once.

Complexity Analysis

OperationTimeSpaceNote
register product or warehouseO(1)O(1)Hash-map insert in the in-memory Repository.
add stockO(1)O(1)Lookup the stock item and update onHand under its lock.
reserve stockO(1 + O)O(1)O observers are notified only when the threshold is crossed; the stock mutation itself is O(1).
release stockO(1)O(1)Decrease reserved quantity under the stock-item lock.
snapshot all stockO(N)O(N)N stock items are copied into immutable snapshots.

The design intentionally locks only one StockItem for reserve, release, and ship. That keeps unrelated SKUs and warehouses independent while preserving the invariant for the hot aggregate.

Extensibility

New replenishment policy

Implement ReplenishmentStrategy for lead-time demand, seasonal buffers, supplier minimum order quantities, or machine-learning forecasts.

New notification channel

Implement InventoryObserver for email, Slack, PagerDuty, metrics, or automatic purchase-order creation without editing stock mutation logic.

Durable persistence

Replace InMemoryInventoryRepository with a SQL repository that uses row locks or optimistic version checks around the stock item.

Multi-warehouse allocation

Add an allocation strategy that chooses the warehouse based on nearest location, shipping cost, or stock age before calling reserve.

Inventory movement ledger

Record every add, reserve, release, and ship operation as an immutable movement event for audits and reconciliation.

Alternative Designs

Database row lock as the concurrency boundary

Instead of an in-memory ReentrantLock, store each stock item in a table and reserve with a transaction that locks the SKU and warehouse row.

Tradeoffs

Works across multiple service instances and survives restarts, but adds database latency and requires careful transaction tuning.

Event-sourced inventory ledger

Append stock movement events and derive current availability from snapshots plus later events.

Tradeoffs

Excellent auditability and replay, but more complex reads and harder real-time reservation guarantees unless projections are strongly consistent.

Reservation tokens with expiry

Create reservation records with expiration times and a sweeper that automatically releases stock after checkout timeouts.

Tradeoffs

Matches e-commerce checkout behavior, but introduces background processing and order-state coordination.

Common Mistakes

  • ×

    Checking available stock and then updating reserved stock in separate unsynchronized steps.

  • ×

    Treating available as a stored field, which can drift from onHand - reserved.

  • ×

    Sending a low-stock alert on every reserve while the item is already below threshold.

  • ×

    Hard-coding reorder quantity inside InventoryService instead of using Strategy.

  • ×

    Letting the service manipulate maps directly instead of depending on a Repository interface.

  • ×

    Mixing reserved and shipped states so cancelled orders accidentally reduce physical stock.

  • ×

    Using one global lock for every SKU and warehouse, which makes unrelated reservations block each other.

Follow-up Interview Questions

QHow would you make this safe across multiple app servers?

Move the concurrency boundary into the Repository with a database transaction, row lock, or compare-and-swap version field on the stock item.

QShould low stock be based on on-hand or available stock?

For order promise accuracy, use available = onHand - reserved. A product can have high on-hand inventory but still be unavailable because most units are reserved.

QHow do you prevent abandoned carts from holding stock forever?

Attach an expiry time to reservations and run a sweeper or scheduled job that releases expired holds through the same releaseStock path.

QHow would you support choosing the best warehouse for an order?

Add a WarehouseAllocationStrategy before reservation. It can consider customer location, shipping cost, stock age, and warehouse load, then reserve against the chosen stock item.

QWhat happens if an observer fails?

In production, observer delivery should be isolated through retries or an outbox. The stock mutation should remain committed even if email or Slack delivery fails.

Production Considerations

Transactions and locking

Use row-level locks, optimistic versions, or atomic conditional updates so multiple service instances cannot oversell the same SKU and warehouse.

Outbox for notifications

Persist low-stock events in the same transaction as the stock change, then deliver them asynchronously to observers for reliable retries.

Idempotency

Accept an order id and operation id so repeated reserve, release, or ship requests do not double-count after client retries.

Audit and reconciliation

Maintain an inventory movement ledger and reconcile it against warehouse scans, supplier receipts, and shipment confirmations.

Metrics

Track reservation failures, low-stock events, observer latency, reorder recommendation volume, and stock drift by warehouse.

What Interviewers Look For

  • Did the candidate identify SKU + warehouse as the stock aggregate?

  • Is the check-then-reserve mutation protected by a single lock or transaction?

  • Are reserve, release, and ship separate verbs with clear invariants?

  • Does low-stock alerting fire on threshold crossing rather than every update?

  • Are Repository, Observer, Strategy, and Singleton used for real seams rather than decoration?

  • Can the design evolve to multi-instance persistence and reservation expiry?

Quiz

0/5 answered

  1. 1.Why does **StockItem.reserve** lock around both the availability check and the reserved update?

  2. 2.Why is available stock computed as **onHand - reserved**?

  3. 3.When should **InventoryObserver** subscribers be notified?

  4. 4.What does **ReplenishmentStrategy** make easy to change?

  5. 5.What role does **InventoryRepository** play?

Practice Variants

Add reservation expiry

Intermediate

Create reservation records with expiry timestamps and a sweeper that releases expired holds through releaseStock.

Add warehouse allocation strategy

Advanced

Choose the best warehouse for an order based on customer location, shipping cost, and current availability before reserving stock.

Add purchase-order generation

Intermediate

Turn low-stock recommendations into purchase orders through an observer, then track supplier receipts back into addStock.

Flashcards

Cheat Sheet

Entities: Product, Warehouse, StockItem, InventoryService, InventoryRepository, InventoryObserver, ReplenishmentStrategy.

Core invariant: available stock is derived as onHand - reserved; reserve must not let available go negative.

Concurrency: lock one StockItem during reserve, release, add, and ship. In production, move the lock to a durable transaction boundary.

Patterns: Observer for low-stock notifications, Strategy for reorder policy, Repository for persistence, Singleton for the shared service facade.

Flows: add stock = increase onHand; reserve = increase reserved; release = decrease reserved; ship = decrease reserved and onHand.

Alert rule: notify only when available crosses from at or above threshold to below threshold, then ask the strategy for a recommendation.

References