Design an Inventory Management System
OOP design for an inventory management system covering product catalog, stock tracking, warehouse management, reorder alerts, and batch operations with audit trails.
The Problem
Your e-commerce company manages 12,000 SKUs across three warehouses. The current system tracks stock as a single integer column in the product table. Last month, a race condition between two checkout threads oversold 300 units of a flash-sale item, and the support team spent four days reconciling manual spreadsheets because nobody could trace when or why stock numbers changed. Reorder decisions happen in a weekly meeting where someone eyeballs a dashboard and sends emails to suppliers.
Inventory management looks simple on the surface (add stock, remove stock, done) but falls apart without an audit trail, multi-warehouse support, and automated reorder logic. Every stock change must be an immutable event, not an in-place mutation.
Design the core classes for an inventory management system that handles product catalog management, stock tracking with movement logs, multi-warehouse transfers, automated reorder alerts, purchase order lifecycle, and a full audit trail.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to turn the vague prompt into a concrete specification. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "Are we managing a single warehouse or multiple warehouses? Can stock be transferred between them?"
Interviewer: "Multiple warehouses. Each warehouse holds its own stock for each product. Transfers between warehouses are a core operation."
Multiple warehouses means every stock record is scoped to a (product, warehouse) pair, not just a product. Transfers create two movements: one OUT of the source and one IN to the destination. That doubles the complexity of a single-warehouse system.
You: "How should we track stock changes? Just update a counter, or keep a history of every movement?"
Interviewer: "Keep a full movement log. Every stock change (receiving, shipping, transfer, manual adjustment) is recorded as an immutable event. The current stock level is derived from the movement log."
This is the critical design decision. An append-only movement log is the source of truth, and the current quantity is a cached derivation. This gives us a complete audit trail and makes reconciliation trivial.
You: "Do we need to track batches or lot numbers? What about perishable items with expiry dates?"
Interviewer: "Not in the initial version. Batch tracking and expiry management are natural extensions but out of scope for now."
Good. Ruling out batch tracking simplifies the model considerably. We can note it in the extensibility section as a follow-up.
You: "How does reordering work? Should the system automatically create purchase orders, or just send alerts?"
Interviewer: "The system should detect low stock based on configurable reorder rules and notify stakeholders. Automatic purchase order creation is a follow-up. For now, the alert triggers and a human reviews it."
Reorder detection fits the Observer pattern. When stock drops below a threshold, fire an alert. Different products may have different reorder strategies (fixed reorder point, economic order quantity, just-in-time), which suggests a Strategy pattern.
You: "What is the lifecycle of a purchase order? Can it be cancelled after submission?"
Interviewer: "A purchase order starts as DRAFT, gets SUBMITTED for approval, then APPROVED, SHIPPED by the supplier, and finally RECEIVED into a warehouse. It can be CANCELLED from DRAFT or SUBMITTED states only."
Six states with restricted transitions. This is a textbook state machine. The key constraint is that cancellation is only valid from early states, not after approval.
You: "Should stock operations be thread-safe? Could two threads try to decrement stock for the same product simultaneously?"
Interviewer: "Yes. Concurrent checkout threads can race on the same product. Stock decrement must be atomic: check availability and decrement in one synchronized operation to prevent overselling."
Thread safety is a non-functional requirement but drives design decisions. The stock adjustment method needs synchronization per product-warehouse pair, not a global lock.
You: "Do we need barcode or SKU support for products?"
Interviewer: "Every product has a unique SKU. Barcode scanning integration is out of scope, but SKU lookup must be efficient."
SKU is the natural unique identifier for products. We will use it for lookups and as a key in stock records.
You: "What units of measure do products use? Are they all 'each', or do some use weight, volume, etc.?"
Interviewer: "For now, all products use a simple integer quantity. Supporting weight-based or volume-based inventory is a possible extension."
Integer quantities keep the stock model simple. We do not need a unit-of-measure abstraction in v1.
Final Requirements
Functional Requirements:
- Manage a product catalog with SKU, name, category, and reorder rules
- Track stock per product per warehouse with an append-only movement log
- Support four movement types: IN (receiving), OUT (shipping), TRANSFER, and ADJUSTMENT
- Transfer stock between warehouses as an atomic two-movement operation
- Detect low stock against configurable reorder rules and fire alerts
- Manage purchase order lifecycle (DRAFT, SUBMITTED, APPROVED, SHIPPED, RECEIVED, CANCELLED)
- Receive purchase order stock into a specific warehouse, creating IN movements
Non-Functional Requirements:
- Thread safety for concurrent stock operations on the same product-warehouse pair
- Extensibility for new reorder strategies without modifying existing alert logic
- Full audit trail: every stock change is traceable to a user, timestamp, and reason
Out of Scope:
- Batch/lot number tracking and expiry dates
- Barcode scanning integration
- Automatic purchase order creation (alerts only)
- UI or API layer
- Persistence and database storage
- Weight-based or volume-based inventory
Interview tip
In a real interview, these requirements come from YOUR questions. Writing them on the whiteboard shows you are methodical. Numbering them lets the interviewer reference "requirement 3" later when they ask follow-ups.
Example Inputs and Outputs
Scenario 1: Receive stock from a supplier
- Input: Receive 500 units of SKU "WIDGET-001" into Warehouse A via Purchase Order PO-1001
- Expected: A StockMovement(IN, 500) is logged. InventoryRecord for (WIDGET-001, Warehouse A) shows quantity 500. PO-1001 transitions to RECEIVED.
- Why: Validates movement logging, inventory record update, and purchase order state transition (requirements 2, 6, 7).
Scenario 2: Ship stock and trigger reorder alert
- Input: Ship 480 units of SKU "WIDGET-001" from Warehouse A. Reorder point is set at 50 units.
- Expected: A StockMovement(OUT, 480) is logged. Quantity drops to 20. A LowStockAlert fires because 20 < 50.
- Why: Validates stock decrement, derived quantity calculation, and observer notification (requirements 2, 3, 5).
Scenario 3: Transfer between warehouses
- Input: Transfer 100 units of SKU "GADGET-007" from Warehouse B to Warehouse C.
- Expected: Two movements are logged: StockMovement(TRANSFER_OUT, 100) at Warehouse B and StockMovement(TRANSFER_IN, 100) at Warehouse C. Both inventory records update atomically.
- Why: Validates multi-warehouse transfer as a paired operation (requirement 4).
Try It Yourself
Try it yourself
Before reading the solution, spend 15-20 minutes sketching your own class diagram. Focus on how stock movements relate to inventory records and how the reorder alert system connects to stock changes. The key question: where does the "current quantity" live, and how does it stay in sync with the movement log?
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Look for nouns in your requirements. A product exists in a catalog. Stock lives in warehouses. Movements record every change. Orders flow through a lifecycle. Alerts fire when thresholds are crossed.
A common mistake is merging Product with InventoryRecord. They have different lifecycles: a Product exists whether or not it has stock anywhere. An InventoryRecord is the per-warehouse stock snapshot. Separating them follows SRP and lets you add warehouses without touching the product catalog.
| Entity | Responsibility | Key attributes |
|---|---|---|
| Product | Catalog entry. Holds SKU, name, category, and reorder config. | sku, name, category, reorderRule |
| Warehouse | Physical location with a unique code. Owns nothing about stock. | code, name, location |
| InventoryRecord | Stock snapshot for one (product, warehouse) pair. Cached quantity. | product, warehouse, quantity |
| StockMovement | Immutable event recording a stock change. Source of truth. | type, quantity, timestamp, performedBy, reason |
| MovementType | Enum: IN, OUT, TRANSFER_OUT, TRANSFER_IN, ADJUSTMENT. | (enum values) |
| ReorderRule | Configures when to reorder. Delegates to a pluggable strategy. | reorderPoint, strategy |
| ReorderStrategy | Strategy interface for reorder calculation. | calculateReorderQuantity() |
| PurchaseOrder | Order to a supplier with guarded lifecycle transitions. | supplier, lines, status |
| PurchaseOrderStatus | Enum: DRAFT, SUBMITTED, APPROVED, SHIPPED, RECEIVED, CANCELLED. | (enum values) |
| StockAlertListener | Observer interface for stock threshold notifications. | onStockAlert(alert) |
Notice we separated StockMovement from InventoryRecord. The movement is the immutable event ("what happened"). The record is the derived state ("what's the current count"). This distinction is the foundation of the entire audit trail.
Step 2: Define Relationships and Class Design
Class Diagram
InventoryService (The Orchestrator)
This is the central class. It coordinates stock operations, records movements, updates cached quantities, and fires alerts. Think of it as the service layer that enforces business rules.
Deriving state from requirements:
| Requirement | What InventoryService must track |
|---|---|
| "Track stock per product per warehouse" | Map of (product, warehouse) to InventoryRecord |
| "Append-only movement log" | List of all StockMovement entries |
| "Fire alerts on low stock" | List of registered StockAlertListener observers |
| "Thread-safe concurrent operations" | Synchronization per inventory record |
Deriving methods from needs:
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.