Warehouse/Product/StockItem/Supplier/Order classes, stock reservation during checkout reusing the seat-lock pattern, and low-stock alerting as an Observer hook.
Published September 23, 2026
class Warehouse { String location; Map<String, StockItem> stockByProductId; }
class Product { String id; String name; int lowStockThreshold; }
class StockItem { Product product; int quantityOnHand; int quantityReserved; } // reserved != on-hand — see below
class Supplier { String name; List<Product> suppliedProducts; }
class Order { List<OrderLine> lines; OrderStatus status; }
class InventoryService {
boolean reserveStock(String productId, int quantity, Warehouse warehouse) {
StockItem item = warehouse.stockByProductId.get(productId);
synchronized (item) { // per-item lock, same principle as per-spot/per-seat locking elsewhere in this course
int available = item.quantityOnHand - item.quantityReserved;
if (available < quantity) return false;
item.quantityReserved += quantity; // reserved, not yet deducted from on-hand
return true;
}
}
void confirmReservation(String productId, int quantity, Warehouse warehouse) {
StockItem item = warehouse.stockByProductId.get(productId);
synchronized (item) {
item.quantityOnHand -= quantity;
item.quantityReserved -= quantity;
}
}
void releaseReservation(String productId, int quantity, Warehouse warehouse) {
synchronized (warehouse.stockByProductId.get(productId)) {
warehouse.stockByProductId.get(productId).quantityReserved -= quantity; // order abandoned/expired — release the hold
}
}
}
Tracking quantityReserved separately from quantityOnHand is the key modeling decision — it's structurally identical to Movie Ticket Booking System's HELD vs BOOKED seat states and Parking Lot's spot-claiming: a reservation temporarily removes stock from what's available to other orders without yet committing to a permanent deduction, so an abandoned checkout can cleanly release the hold via releaseReservation() rather than needing to "add back" a quantity that was never actually removed from quantityOnHand in the first place.
interface LowStockListener { void onLowStock(Product product, int currentQuantity); }
class InventoryService {
private final List<LowStockListener> listeners = new CopyOnWriteArrayList<>();
void confirmReservation(String productId, int quantity, Warehouse warehouse) {
StockItem item = warehouse.stockByProductId.get(productId);
synchronized (item) {
item.quantityOnHand -= quantity;
item.quantityReserved -= quantity;
if (item.quantityOnHand < item.product.lowStockThreshold) {
listeners.forEach(l -> l.onLowStock(item.product, item.quantityOnHand)); // notify, don't decide what happens next
}
}
}
}
InventoryService doesn't know or care what happens on low stock — sending a supplier reorder email, paging an ops team, updating a dashboard — it just fires the event (Observer Pattern) and lets registered listeners decide. Coupling reorder-email logic directly into confirmReservation() would violate Single Responsibility (inventory tracking and notification delivery are genuinely separate concerns) and make adding a second reaction (e.g. also updating a dashboard) require editing InventoryService itself rather than just registering a second listener.
Q: What happens if reserveStock() succeeds but the order is never confirmed or explicitly released (e.g. the application crashes)? A: Same failure mode as any hold-with-expiry pattern in this course — a reservation needs a timeout/expiry mechanism (see Movie Ticket Booking System's scheduleExpiry discussion) so an abandoned reservation doesn't permanently lock stock away; a durable, sweep-based expiry (not just an in-process timer) matters even more here since inventory holds can be longer-lived than a seat-booking flow.
Q: Why lock per-StockItem rather than per-Warehouse? A: The same fine-grained-locking argument as Parking Lot — Implementation: locking the whole warehouse would serialize reservations across every unrelated product, while per-item locking only contends when two orders target the exact same product, letting unrelated products' reservations proceed fully in parallel.
Q: How would this design handle a single order needing multiple products from potentially different warehouses? A: Each product's reservation would need its own reserveStock() call, and the order as a whole should only confirm once every line's reservation succeeds (an all-or-nothing multi-item commit) — the same atomicity concern as Airline Booking's multi-leg itinerary, applied to order lines instead of flight legs.
Q: Does firing the low-stock event synchronously inside the synchronized block risk anything? A: Yes — a slow or blocking listener would hold the StockItem's lock for the listener's entire execution time, blocking other threads trying to reserve/release that same product. A safer design dispatches listener notifications asynchronously (via an executor, see ExecutorService & Thread Pools) after releasing the lock, rather than synchronously inside the critical section.