How to Design an Inventory Management System?
Learn how to design an inventory management system with a stock ledger, reserve/commit flows, event propagation, and reconciliation.
Expected Interview Answer
An inventory management system is designed around a source-of-truth stock ledger per SKU-per-warehouse with atomic reserve/commit/release operations, an event stream that propagates stock changes to downstream services like search and pricing, and reconciliation jobs that catch drift between physical counts and the recorded ledger.
Rather than storing a single mutable “quantity” field that many services write directly, a robust design uses an append-only ledger of stock movements (received, reserved, committed/shipped, released, adjusted) per SKU per location, so the current quantity is always derivable and auditable, and concurrent updates use atomic conditional decrements (or optimistic locking) to prevent overselling. When an order is placed, the system reserves stock (a soft hold with an expiry) rather than immediately committing it, so an abandoned cart automatically releases inventory back to the pool; the reservation is only committed once payment and fulfillment proceed. Stock changes are published as events (via an outbox pattern to guarantee they are not lost) so downstream consumers — search indexes, pricing, storefront caches, replenishment/forecasting systems — stay eventually consistent without hammering the ledger directly. Because physical counts can drift from the recorded ledger (theft, damage, scanning errors), periodic cycle counts and reconciliation jobs compare physical to recorded stock and generate adjustment entries rather than silently overwriting the ledger.
- An append-only ledger per SKU/location makes stock levels auditable and reconstructable
- Reserve-then-commit with expiring holds prevents overselling from abandoned carts
- Event-driven propagation (outbox pattern) keeps search/pricing consistent without direct ledger coupling
- Reconciliation via cycle counts catches drift between physical and recorded inventory
AI Mentor Explanation
Designing an inventory management system is like a groundskeeper’s ball-tracking ledger, recording every ball issued to a match, retrieved after the innings, or lost over the boundary, rather than just keeping one mutable “balls remaining” number. Each ball handed to the umpires is a soft reservation until the match confirms it was used, and if a match is abandoned, those balls are released back to the store rather than vanishing from the count. Periodically the groundskeeper does a physical count against the ledger to catch any balls that went missing without being logged. That auditable, event-based tracking with periodic reconciliation is exactly how inventory management systems stay accurate.
Step-by-Step Explanation
Step 1
Model stock as an append-only ledger
Record every movement (received, reserved, committed, released, adjusted) per SKU per location instead of one mutable quantity field.
Step 2
Reserve before commit
An order places a soft, expiring hold on stock; it only becomes a committed decrement once payment and fulfillment proceed.
Step 3
Propagate changes via events
An outbox pattern publishes stock-change events reliably so search, pricing, and storefront caches stay eventually consistent.
Step 4
Reconcile against physical counts
Periodic cycle counts compare physical stock to the ledger and generate adjustment entries to correct drift, never silent overwrites.
What Interviewer Expects
- Proposes an append-only ledger per SKU/location instead of a single mutable quantity field
- Explains reserve-then-commit with expiring holds to prevent overselling from abandoned carts
- Describes event-driven propagation (outbox pattern) to keep downstream systems consistent
- Mentions reconciliation/cycle counts to handle drift between physical and recorded stock
Common Mistakes
- Using a single “quantity” column with direct writes from many services, causing race conditions
- Immediately committing stock on order placement instead of using an expiring reservation
- Coupling downstream systems (search, pricing) directly to the ledger instead of via events
- Assuming physical and recorded stock will always match without reconciliation processes
Best Answer (HR Friendly)
“An inventory management system tracks stock as a history of events — received, reserved, shipped, adjusted — rather than one number that everything overwrites, which makes it possible to audit exactly what happened and prevents overselling. When someone places an order, we put a temporary hold on the stock instead of immediately committing it, so an abandoned cart automatically frees that inventory back up, and we run regular physical counts to catch and correct any drift between what the system thinks we have and what is actually on the shelf.”
Code Example
def reserve_stock(sku, location_id, qty, order_id, hold_minutes=15):
updated = db.execute(
"UPDATE stock_ledger SET reserved = reserved + %s, version = version + 1 "
"WHERE sku = %s AND location_id = %s "
"AND (on_hand - reserved) >= %s AND version = %s",
[qty, sku, location_id, qty, current_version(sku, location_id)],
)
if updated.row_count == 0:
raise InsufficientStockError(sku)
db.insert_event("stock.reserved", {
"sku": sku, "locationId": location_id, "qty": qty,
"orderId": order_id, "expiresAt": now() + hold_minutes * 60,
}) # outbox row, published reliably to downstream consumers
def commit_reservation(order_id, sku, location_id, qty):
db.execute(
"UPDATE stock_ledger SET on_hand = on_hand - %s, reserved = reserved - %s "
"WHERE sku = %s AND location_id = %s",
[qty, qty, sku, location_id],
)
db.insert_event("stock.committed", {"orderId": order_id, "sku": sku, "qty": qty})
def release_expired_reservations():
expired = db.query("SELECT * FROM reservations WHERE expires_at < NOW() AND status = 'held'")
for r in expired:
db.execute(
"UPDATE stock_ledger SET reserved = reserved - %s WHERE sku = %s AND location_id = %s",
[r.qty, r.sku, r.location_id],
)
db.insert_event("stock.released", {"orderId": r.order_id, "sku": r.sku})Follow-up Questions
- How would you prevent overselling the last unit of a popular SKU across multiple warehouses?
- What happens to reserved stock if the order is abandoned before checkout completes?
- How do you reconcile physical stock counts with the recorded ledger without losing audit history?
- How would you scale this system for a retailer with millions of SKUs across hundreds of warehouses?
MCQ Practice
1. Why use an append-only ledger of stock movements instead of a single mutable quantity field?
An append-only ledger records every movement as an event, making the current quantity derivable and every change auditable, unlike a single overwritten field.
2. Why does an order reserve stock with an expiring hold instead of committing it immediately?
A soft, expiring reservation lets stock return to the pool automatically if the customer never completes checkout, avoiding permanently locked inventory.
3. What problem does periodic cycle counting and reconciliation solve?
Physical stock can drift from recorded stock due to theft, damage, or scanning errors; reconciliation compares the two and logs corrective adjustment entries.
Flash Cards
Why use an append-only ledger for inventory? — It makes stock auditable and reconstructable, and avoids race conditions from direct overwrites of a single quantity field.
Why reserve stock before committing it? — A soft, expiring hold prevents overselling while letting abandoned carts automatically release stock back to the pool.
How do downstream systems (search, pricing) stay consistent? — Via an outbox pattern publishing stock-change events reliably, keeping them eventually consistent without direct ledger coupling.
What handles drift between physical and recorded stock? — Periodic cycle counts and reconciliation jobs that generate adjustment entries instead of silently overwriting the ledger.