What Is a Supply Chain Management Service?
A supply chain management (SCM) service coordinates the flow of goods from suppliers to warehouses: maintaining a supplier catalog, creating and tracking purchase orders, recording shipments, processing goods receipts, updating inventory, and alerting on exceptions. It is a strong low level design interview topic because it involves multi-step workflows, state machines, and event-driven integration.
Functional Requirements
- Manage a supplier catalog with contact information, lead times, and item offerings.
- Create and manage purchase orders (POs) sent to suppliers.
- Track shipments associated with POs from dispatch to delivery.
- Record goods receipt: confirm items received, note discrepancies.
- Update inventory levels on receipt.
- Alert on late shipments, quantity mismatches, and quality rejections.
Non-Functional Requirements
- PO and shipment state transitions must be auditable and irreversible (append-only history).
- Alert delivery must be reliable (at-least-once with deduplication).
- Inventory updates must be atomic with goods receipt recording.
Core Entities
Supplier
id UUID PK
name VARCHAR(128)
contact_email VARCHAR(256)
lead_time_days INT
is_active BOOLEAN
created_at TIMESTAMP
SupplierItem
id UUID PK
supplier_id UUID FK -> Supplier
sku VARCHAR(64)
unit_cost DECIMAL(12,2)
currency CHAR(3)
moq INT -- minimum order quantity
PurchaseOrder
id UUID PK
po_number VARCHAR(32) UNIQUE
supplier_id UUID FK -> Supplier
status ENUM('DRAFT','SENT','ACKNOWLEDGED','PARTIALLY_SHIPPED','SHIPPED','RECEIVED','CLOSED','CANCELLED')
expected_date DATE
created_at TIMESTAMP
updated_at TIMESTAMP
POLineItem
id UUID PK
po_id UUID FK -> PurchaseOrder
sku VARCHAR(64)
quantity_ordered INT
unit_cost DECIMAL(12,2)
quantity_received INT DEFAULT 0
Shipment
id UUID PK
po_id UUID FK -> PurchaseOrder
tracking_number VARCHAR(128)
carrier VARCHAR(64)
status ENUM('DISPATCHED','IN_TRANSIT','OUT_FOR_DELIVERY','DELIVERED','EXCEPTION')
dispatched_at TIMESTAMP
delivered_at TIMESTAMP NULL
ShipmentItem
shipment_id UUID FK -> Shipment
sku VARCHAR(64)
quantity INT
GoodsReceipt
id UUID PK
shipment_id UUID FK -> Shipment
received_by VARCHAR(64)
received_at TIMESTAMP
notes TEXT
GoodsReceiptLine
id UUID PK
receipt_id UUID FK -> GoodsReceipt
sku VARCHAR(64)
quantity_expected INT
quantity_received INT
quantity_rejected INT DEFAULT 0
rejection_reason VARCHAR(256) NULL
Purchase Order State Machine
Valid transitions:
DRAFT -> SENT
SENT -> ACKNOWLEDGED | CANCELLED
ACKNOWLEDGED -> PARTIALLY_SHIPPED | SHIPPED | CANCELLED
PARTIALLY_SHIPPED -> SHIPPED
SHIPPED -> RECEIVED
RECEIVED -> CLOSED
Persist every transition to a POStatusHistory table (po_id, from_status, to_status, changed_by, changed_at) for a full audit trail. Reject invalid transitions at the service layer before touching the PO row.
Shipment Tracking
Carrier webhooks push status updates (IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION). The SCM service exposes a webhook endpoint that:
- Verifies carrier HMAC signature.
- Upserts shipment status.
- If DELIVERED, triggers the goods receipt workflow notification.
- If EXCEPTION (e.g., customs hold, lost parcel), creates an Alert record and notifies the procurement team.
For carriers without webhook support, a polling job calls the carrier API every N minutes and applies the same upsert logic.
Goods Receipt Flow
- Warehouse staff open a GoodsReceipt against a Shipment.
- For each ShipmentItem, enter quantity_received and quantity_rejected.
- On confirmation, begin a transaction: a. Insert GoodsReceipt and GoodsReceiptLine rows. b. Update POLineItem.quantity_received for each SKU. c. Increment inventory (call Inventory Service or update local stock table). d. If all line items are fully received, advance PO status to RECEIVED. e. If quantity discrepancy exists (received != expected), insert a Discrepancy Alert. f. Commit.
Inventory Update
Inventory update is part of the same database transaction as goods receipt when both services share a DB. If inventory is a separate service, use an outbox pattern: write an InventoryCreditEvent row in the same transaction, then a relay process publishes it to the message bus. The inventory service consumes it and applies the credit idempotently using the GoodsReceiptLine ID as the idempotency key.
Exception Alerting
Alert triggers:
- Late shipment: scheduled job checks POs where
expected_date < TODAY AND status NOT IN ('RECEIVED','CLOSED','CANCELLED'). - Quantity mismatch: detected during goods receipt (quantity_received != quantity_expected).
- Quality rejection: quantity_rejected > 0 on any receipt line.
- Carrier exception: EXCEPTION status from carrier webhook.
Each alert is inserted into an Alert table and published to a notification topic. The Alert table acts as the outbox for deduplication: insert with a UNIQUE key on (alert_type, reference_id) to avoid duplicate notifications from repeated job runs.
API Design
POST /suppliers -- create supplier
POST /suppliers/{id}/items -- add item offering
POST /purchase-orders -- create PO
POST /purchase-orders/{id}/send -- transition DRAFT -> SENT
POST /purchase-orders/{id}/acknowledge -- SENT -> ACKNOWLEDGED
GET /purchase-orders/{id} -- PO detail with line items
POST /shipments -- create shipment against PO
POST /shipments/{id}/status -- carrier webhook or manual update
POST /shipments/{id}/receive -- create goods receipt
GET /purchase-orders/{id}/history -- audit trail
Interview Tips
- Model the PO lifecycle as an explicit state machine and defend each valid transition.
- Emphasize that inventory credit and goods receipt must be atomic — the outbox pattern is the clean answer when services are separate.
- Show the Alert deduplication strategy; interviewers will probe for double-alert scenarios.
- Discuss the webhook vs. polling trade-off for carrier tracking and when you would use each.
Frequently Asked Questions
What is a supply chain management service in system design?
A supply chain management service coordinates the flow of goods from suppliers through warehouses to end customers. In system design terms it is a collection of bounded contexts: procurement (purchase orders, supplier contracts), inventory (stock levels per location), fulfillment (pick, pack, ship), and logistics (carrier integration, tracking). Each context owns its data and exposes events that downstream contexts consume. The architecture favors event-driven patterns because supply chain operations are long-running and involve external parties; a purchase order placed today may not be received for weeks, so synchronous RPC across the full flow is impractical.
How does a purchase order state machine work?
A purchase order (PO) moves through a well-defined set of states: Draft, Submitted, Acknowledged, Partially Received, Fully Received, Invoiced, and Closed (or Cancelled at any pre-receipt stage). Each transition is triggered by an event: a buyer submits the draft, the supplier sends an acknowledgment EDI message, warehouse staff record a goods receipt, and finance records invoice payment. The state machine is enforced at the service layer; invalid transitions return errors rather than silently corrupting state. All transitions are persisted as immutable events in an append-only store, giving a full audit trail. Timeout monitors trigger alerts or automated follow-ups when a PO remains in Submitted without acknowledgment beyond the SLA window.
How do you track shipments across multiple carriers?
Carrier integration is abstracted behind a unified tracking adapter interface. Each carrier (UPS, FedEx, DHL, regional last-mile providers) has a concrete adapter that polls or receives webhooks from the carrier’s API and maps carrier-specific status codes to a canonical event schema (e.g., IN_TRANSIT, OUT_FOR_DELIVERY, DELIVERED, EXCEPTION). A tracking aggregator service subscribes to all adapter outputs, deduplicates events by tracking number and timestamp, and publishes normalized events to an internal bus. Downstream services (customer notifications, analytics, SLA monitoring) consume from the bus without knowing which carrier handled the shipment. Polling intervals are backed off exponentially once a shipment reaches a terminal state to reduce unnecessary API calls.
How do you handle inventory discrepancies between systems?
Discrepancies arise when the warehouse management system (WMS), the order management system (OMS), and the ERP disagree on stock levels due to delayed syncs, failed messages, or manual adjustments. The canonical resolution pattern is to designate one system as the source of truth for each data attribute and have others reconcile toward it. Periodic reconciliation jobs compare counts across systems and emit discrepancy events that trigger investigation workflows. Physical cycle counts feed corrections back as signed adjustment transactions rather than absolute overwrites, preserving the audit trail. For real-time decisions (can this order be fulfilled?), the OMS reserves inventory via soft allocation; the WMS confirms or rejects the reservation when it processes the pick task, and the OMS rolls back if rejected. Alerting on discrepancy magnitude and frequency helps surface systemic integration bugs early.
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Shopify Interview Guide
See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering
See also: Databricks Interview Guide 2026: Spark Internals, Delta Lake, and Lakehouse Architecture