Skip to main content

Inventory Management

Design the stockroom behind an online store: items sit in warehouses, quantities go up on delivery and down on sale, and someone needs to be told when a shelf is running low. The trap in this prompt is treating "reorder" as a side effect buried in sell() - it's a policy that changes per item, and it deserves its own seam.

Requirements

Functional

  • A Warehouse holds StockLevels: one entry per Item, tracking on-hand quantity.
  • Selling an item decrements its stock level; receiving a delivery increments it.
  • A sale that would take stock below zero is rejected, not clamped to zero.
  • Each item defines its own reorder threshold; when a stock level crosses at or below it, the warehouse should flag that item for restocking.

Non-functional

  • The reorder rule (fixed threshold today, demand-based forecasting tomorrow) must be swappable per item without changing Warehouse or StockLevel.
  • Checking "does anything need restocking right now" must not re-scan every item in the warehouse on every sale; it should be answered from a small, kept-current set.

Design

Each Item carries a ReorderStrategy instead of a plain numeric threshold field - exactly the same move Parking Lot makes for FeeStrategy: the one part of the domain that legitimately varies (today it's a fixed number, tomorrow it might read a demand forecast) gets isolated behind one method, shouldReorder(currentQuantity), so Warehouse never branches on which kind of item it's holding.

Order systemWarehouseStockLevelReorderStrategysell(item, qty)1adjust(-qty)2quantity += delta3shouldReorder(quantity)4needsRestock.add(item)5
  1. 1The order system never touches a StockLevel directly - every mutation is routed through the warehouse.
  2. 2The warehouse finds the right stock level and asks it to change quantity by a signed amount.
  3. 3A negative result throws here, before anything else happens - no partial update to clean up.
  4. 4After a successful adjustment, the stock level asks the item’s own strategy whether this crossed the line.
  5. 5If the strategy says yes, the warehouse updates its restock set right here - no separate scan ever needed.

StockLevel is the only place quantity is mutated; Warehouse maintains a needsRestock set that gets updated at that single mutation point, so it never has to recompute the whole warehouse's status from scratch.

Class diagram

«interface»ReorderStrategy+ shouldReorder(qty): boolWarehouse- levels: Map<Item, StockLevel>- needsRestock: Set<Item>+ sell(item, qty)+ receiveDelivery(item, qty)+ getRestockList(): Set<Item>StockLevel- item: Item- quantity: int+ adjust(delta): boolItem- sku: string- name: string- reorderStrategy: ReorderStrategyFixedThresholdReorder+ shouldReorder(qty): bool
implementsuses
Warehouse mutates StockLevel, which consults the item's ReorderStrategy and reports back for the restock set.

Code

import java.util.*;
 
interface ReorderStrategy {
boolean shouldReorder(int quantity);
}
 
class FixedThresholdReorder implements ReorderStrategy {
private final int threshold;
 
FixedThresholdReorder(int threshold) {
this.threshold = threshold;
}
 
public boolean shouldReorder(int quantity) {
return quantity <= threshold;
}
}
 
class Item {
final String sku;
final String name;
final ReorderStrategy reorderStrategy;
 
Item(String sku, String name, ReorderStrategy reorderStrategy) {
this.sku = sku;
this.name = name;
this.reorderStrategy = reorderStrategy;
}
}
 
class StockLevel {
final Item item;
private int quantity;
 
StockLevel(Item item, int quantity) {
this.item = item;
this.quantity = quantity;
}
 
void adjust(int delta) {
int result = quantity + delta;
if (result < 0) {
throw new IllegalStateException("Insufficient stock for " + item.name);
}
quantity = result;
}
 
int getQuantity() { return quantity; }
 
boolean needsRestock() {
return item.reorderStrategy.shouldReorder(quantity);
}
}
 
class Warehouse {
private final Map<Item, StockLevel> levels = new HashMap<>();
private final Set<Item> needsRestock = new HashSet<>();
 
void stock(Item item, int initialQuantity) {
levels.put(item, new StockLevel(item, initialQuantity));
}
 
void sell(Item item, int qty) {
adjust(item, -qty);
}
 
void receiveDelivery(Item item, int qty) {
adjust(item, qty);
}
 
private void adjust(Item item, int delta) {
StockLevel level = levels.get(item);
level.adjust(delta);
if (level.needsRestock()) {
needsRestock.add(item);
} else {
needsRestock.remove(item);
}
}
 
Set<Item> getRestockList() {
return needsRestock;
}
}

Design decisions

  • Reorder logic is a ReorderStrategy on the item, not a threshold field read by Warehouse. A number would handle "restock at 10 units," but demand-based forecasting needs history and a formula, not a comparison - giving every item a strategy object means FixedThresholdReorder and a future DemandForecastReorder implement the same one-method interface, and Warehouse calls it identically either way.
  • StockLevel.adjust() is the single mutation point for quantity, and it's also where the restock set gets updated. Every increment or decrement - sale, delivery, return - funnels through one method, so "did this stock level just cross into reorder territory" is checked exactly once per change instead of being re-derived by every caller.
  • A sale that would go negative throws, it doesn't clamp to zero. Clamping would silently lie about how many units actually left the warehouse (useful for accounting and for whoever is reconciling the delivery that's now short); making it a rejected operation forces the caller to handle "we don't have enough" explicitly rather than discovering it in a report later.
  • What's missing for a real system: concurrent sales against the same StockLevel need the decrement-and-check to be atomic (a lock per stock level, or a compare-and-swap on quantity) to avoid overselling under load, and multi-warehouse fulfillment (pick the nearest warehouse with stock) needs a routing layer this design doesn't include - both skipped to keep the reorder-strategy seam the whole focus.
0%0 of 122 pages studied