Warehouse / Fulfillment — Designed in Stages
You don’t need to design for scale on day one.
Define what you need—receive stock, maintain inventory levels, create pick lists for orders, pick and pack, ship, and optionally route orders to the right warehouse—then build the simplest thing that works and evolve as volume and number of locations grow.
Here we use a warehouse or fulfillment system (e-commerce, retail, Amazon-style) as the running example: warehouses, inventory per SKU, orders, pick lists, and shipments. The same staged thinking applies to any system that holds stock and fulfills orders: inventory consistency (no oversell), pick efficiency (batching, routing), and throughput are central.
Requirements and Constraints (no architecture yet)
Section titled “Requirements and Constraints (no architecture yet)”Functional Requirements
- Receive stock — record incoming inventory (purchase order, shipment received); update inventory by SKU and location; support adjustments and returns.
- Inventory levels — query current quantity per SKU per warehouse (or globally); support available-to-sell (ATS) or available-to-promise (ATP) after reserving for orders.
- Order → pick list — when an order is placed, create a pick list (which items from which locations); allocate or reserve inventory so it isn’t oversold; single or multi-line orders.
- Pick and pack — warehouse workers (or automation) pick items per pick list, pack into shipment; confirm pick/pack; update inventory on ship (deduct) and mark order as shipped.
- Ship — generate shipping label (carrier integration); mark shipment dispatched; optional tracking and delivery events.
- Order routing (multi-warehouse) — decide which warehouse fulfills an order (nearest, or by stock); split orders across warehouses if needed.
Quality Requirements
- Inventory consistency (no oversell) — never promise or ship more than physically available; reserve inventory on order (or allocate at pick/ship); use transactions or optimistic locking so concurrent orders don’t over-allocate.
- Pick efficiency — batching (multiple orders in one pick wave), routing (path through warehouse), and slotting (where SKUs live) affect time and cost; improve as volume grows.
- Throughput — orders per day, picks per hour; system must support peak without becoming bottleneck; async or queue for heavy steps.
- Expected scale — SKU count, warehouses, orders per day, lines per order, number of pickers.
Key Entities
- Warehouse — a physical location (or logical fulfillment node); warehouse_id, address, optional capacity or type.
- Inventory / SKU — stock keeping unit; sku_id, warehouse_id, quantity (on_hand), optional reserved quantity; updated on receive, reserve, pick, ship, adjust.
- Order — customer order; order_id, lines (sku_id, qty), status (pending, allocated, picking, packed, shipped); optional ship_to address for routing.
- Pick list — work unit for picking; links order (or order lines) to locations and quantities; status (created, in_progress, picked, packed); one pick list per order or batched.
- Shipment — outbound shipment; shipment_id, order_id(s), carrier, tracking_id, status; created at pack/ship; inventory deducted when shipment is confirmed.
Primary Use Cases and Access Patterns
- Receive stock — write path; receive event (PO or receipt); update inventory (add to on_hand); optional lot/serial; idempotent by receipt id.
- Inventory levels — read path; query by sku_id, warehouse_id (or all); available = on_hand − reserved; used for ATS and order allocation.
- Order placement — write path; create order; allocate or reserve inventory (decrement available, increment reserved) in transaction; create pick list; fail if insufficient stock.
- Pick and pack — write path; worker scans/confirms pick; update pick list status; on pack/ship: create shipment, deduct reserved (and on_hand), update order status; integrate carrier for label.
- Order routing — read + write; given order (ship-to, lines), choose warehouse(s) with stock and optionally nearest; create pick list at chosen warehouse(s); may split order.
Given this, start with the simplest MVP: one API, one DB, inventory per SKU per warehouse, order → create pick list and reserve inventory, pick and pack (manual or simple flow), update inventory on ship—then add multi-warehouse routing, batching and wave picking, and carrier integration as volume grows.
Stage 1 — MVP (simple, correct, not over-engineered)
Section titled “Stage 1 — MVP (simple, correct, not over-engineered)”Goal
Ship working fulfillment: receive stock, see inventory levels, place orders that create pick lists and reserve stock, pick and pack, and ship with inventory deduction. One API, one DB; single warehouse; no oversell.
Components
- API — REST or similar; auth (admin, worker); receive stock (warehouse_id, sku_id, qty, optional receipt_id); get inventory (sku_id, warehouse_id or list); create order (lines: sku_id, qty) → allocate inventory, create pick list, return order_id; get pick list (order_id or pick_list_id); confirm pick / pack (pick_list_id, optional scan); ship (order_id, carrier, tracking) → deduct inventory, update order. Single server or small cluster.
- DB — warehouses; inventory (warehouse_id, sku_id, on_hand, reserved); orders (id, status, created_at); order_lines (order_id, sku_id, qty); pick_lists (id, order_id, status); pick_list_lines (pick_list_id, sku_id, qty, location optional); shipments (id, order_id, carrier, tracking_id, status). Index inventory by (warehouse_id, sku_id); index orders by status; index pick_lists by order_id and status.
- Allocate on order — in a transaction: for each line check available (on_hand − reserved) ≥ qty; increment reserved for that line; create order and pick list; if any line fails, rollback. Prevents oversell at order time.
- Pick and pack flow — worker gets pick list; picks items (manual or scan); confirm pick; pack; on ship: create shipment row, set reserved → 0 and on_hand -= qty for each line (transaction), set order and pick list status to shipped.
- Single warehouse — all inventory and fulfillment in one warehouse_id; no routing logic at MVP.
Minimal Diagram
Order system / Worker | v+-----------------+| API |+-----------------+ | vDB (warehouses, inventory, orders, pick_lists, shipments) - receive → +on_hand - order → reserve (reserved += qty), create pick list - ship → reserved -= qty, on_hand -= qty | vSingle warehousePatterns and Concerns (don’t overbuild)
- No oversell: allocate (reserve) in same transaction as order create; use row-level lock or optimistic lock on inventory row so two concurrent orders don’t both allocate the same last unit.
- Idempotency: receive stock with receipt_id so duplicate receipt doesn’t double-count; ship with idempotency key if needed.
- Basic monitoring: order rate, allocation failures (insufficient stock), pick-to-ship time, inventory accuracy.
Why This Is a Correct MVP
- One API, one DB, inventory per SKU per warehouse, order → allocate + pick list, pick and pack → ship with deduction → enough to run a single-warehouse fulfillment operation; easy to reason about.
- Vertical scaling and single warehouse buy you time before you need multi-warehouse routing, wave picking, and carrier APIs.
Stage 2 — Growth Phase (multi-warehouse, routing, batching, carrier)
Section titled “Stage 2 — Growth Phase (multi-warehouse, routing, batching, carrier)”What Triggers the Growth Phase?
- Multiple warehouses; need to decide which warehouse fulfills an order (by stock and optionally distance); may split order across warehouses.
- Order volume grows; need batching (wave picking) — group orders into a wave, generate pick list per wave or optimize pick path; improve picker efficiency.
- Need inventory reservation held from order until ship (or timeout); release if order cancelled.
- Need carrier integration: generate label, get tracking, update shipment status from carrier webhooks.
Components to Add (incrementally)
- Multi-warehouse — inventory and pick lists per warehouse; order can be fulfilled from one or more warehouses; routing step chooses warehouse(s) per order.
- Order routing — given order (ship-to address, lines), query which warehouses have stock for each line; choose “nearest” (by zone or distance) or “single warehouse if possible” to minimize split; create pick list at selected warehouse(s); if split, multiple pick lists and shipments for one order.
- Batching and wave picking — group N orders into a wave; generate pick list per wave (or per order within wave) with pick path or location list; workers pick by wave; reduces walking and improves throughput.
- Inventory reservation — on order, reserve for a TTL (e.g. 30 min payment window); on payment (or confirm), keep reserved until ship; on cancel or timeout, release reservation (reserved -= qty).
- Carrier integration — call carrier API for label (rate, label image); store tracking_id; optional webhook for delivery status; update shipment and notify.
Growth Diagram
Order system / Workers | v+-----------------+| API |+-----------------+ | | v vOrder routing Wave / batch job(which warehouse) (group orders, pick path) | | v vDB (multi-warehouse inventory, orders, pick_lists, shipments) | | v vReservation (order → reserve; cancel → release) | vCarrier API (label, tracking)Patterns and Concerns to Introduce (practical scaling)
- Routing rules: prefer single-warehouse fulfillment to avoid split; else choose by distance or cost; consider lead time (in-transit from warehouse to customer).
- Pick path: sort pick list lines by location (zone, aisle, bin) to minimize walking; optional algorithm (e.g. nearest-neighbor) or simple zone order.
- Monitoring: allocation success rate, split order rate, pick rate (units per hour), carrier label and tracking errors.
Still Avoid (common over-engineering here)
- Full optimization (fill truck, route pickers with TSP) until data and volume justify it.
- Automation (robots, conveyors) as a system-design dependency; treat as operational upgrade; system still needs inventory and pick list correctness.
- Real-time inventory sync across many systems until you have clear consistency requirements.
Stage 3 — Advanced Scale (optimization, automation, reconciliation)
Section titled “Stage 3 — Advanced Scale (optimization, automation, reconciliation)”What Triggers Advanced Scale?
- Need to optimize: fill trucks (batch shipments by destination), optimize picker routes (TSP or heuristics), slotting (where to store SKUs for fast pick).
- Automation: robots bring pods to picker, or conveyors move items; system sends work to automation and receives confirmations; same inventory and pick list model, different execution.
- Real-time or near-real-time inventory sync across WMS, order management, and stores; reconciliation (physical count vs system) and cycle counts.
Components (common advanced additions)
- Optimization — fill truck: batch shipments by carrier and destination to reduce cost; route pickers: optimize pick path per wave (TSP or zone-based); slotting: recommend or assign SKU to location by velocity; run as batch jobs or optimization service.
- Automation (robots, conveyors) — system emits “pick” or “move” tasks to automation layer; automation confirms completion; inventory and pick list state updated on confirm; handle failures (retry, reassign).
- Real-time inventory sync — inventory changes (receive, ship, adjust) published to event stream or API; order management and other systems consume for ATS; eventual consistency with reconciliation.
- Reconciliation — cycle count or full physical count; compare system on_hand to physical; adjust (write-off, found); audit trail; investigate discrepancies.
Advanced Diagram (conceptual)
Orders / Workers / Automation | v+-----------------+| API / WMS |+-----------------+ | | | v v vRouting Wave + pick path Optimization (fill truck, | (batch) slotting, routes) v | |DB (inventory, orders, pick_lists, shipments) | | | v v vCarrier Automation Event stream (inventory | (robots, etc.) sync, reconciliation) vReconciliation (cycle count, adjust)Patterns and Concerns at This Stage
- Inventory consistency: with many writers (receive, ship, adjust, returns), use transactions and optional event log for audit; reconciliation corrects drift.
- Automation interface: define task schema (pick from A to B, move to pack); idempotent confirm; handle partial failure and re-queue.
- SLO-driven ops: order-to-ship time, pick accuracy, inventory accuracy (after reconciliation), carrier SLA; error budgets and on-call.
Summarizing the Evolution
Section titled “Summarizing the Evolution”MVP delivers warehouse fulfillment with one API, one DB, inventory per SKU per warehouse, order → allocate and create pick list, pick and pack, and ship with inventory deduction. No oversell; single warehouse. That’s enough to run a small fulfillment operation.
As you grow, you add multi-warehouse, order routing (nearest or by stock), batching and wave picking, inventory reservation with release, and carrier integration. You keep inventory consistent and improve pick efficiency.
At advanced scale, you add optimization (fill truck, pick routes, slotting), automation (robots, conveyors), real-time inventory sync, and reconciliation. You scale throughput and accuracy without over-building on day one.
This approach gives you:
- Start Simple — API + DB, receive, inventory, order → pick list → ship; no oversell; ship and learn.
- Scale Intentionally — add multi-warehouse and routing when you have multiple locations; add batching when pick volume demands it.
- Add Complexity Only When Required — avoid optimization and automation until operations and data justify them; keep inventory consistency and pick list correctness first.