•CASE STUDY

Perishable-Goods Inventory and Location Tracking

4 min read·665 words·Intermediate

Asked at

1 candidate report in Jun 2026

How to use this case study

SDE-2 / Mid

  • Model inventory by lot (batch with an expiry date) and location
  • Record movements as events (received, moved, sold, discarded)

SDE-3 / Senior

  • Allocate stock first-expiring-first-out (FEFO)
  • Keep stock counts correct under concurrent scans
  • Alert on items near expiry
  • Reconcile with physical counts

Staff / Principal

  • Discuss a distributed network of warehouses
  • Stores and trucks
  • Offline scanners
  • Cold-chain temperature data
  • Forecasting to reduce waste

Problem RestatementProblem

Amazon asked: design a system that tracks inventory and location of perishable goods (milk, produce, medicine) across a distributed retail network: warehouses, trucks and stores. Unlike normal inventory, each unit has an expiry date, so the system must know how many of each product we have, where, and when each batch expires, send the soonest-expiring stock first, and alert before things spoil.

RequirementsRequirements

  • Receive stock in lots (batches) with an expiry date, quantity and location.
  • Track movements: warehouse → truck → store shelf, sold, returned, discarded (expired or damaged).
  • Real-time stock per product per location, with an expiry breakdown.
  • Allocate orders and transfers FEFO (first-expiring, first-out).
  • Alerts: "300 units at Store 12 expire within 2 days" (to discount or move them).
  • Scanners at locations may be offline for a while.

Data ModelData model

products:   sku, name, shelf_life_days, storage_temp_range
locations:  location_id, type (warehouse|truck|store), region
lots:       lot_id, sku, expiry_date, received_at, supplier
stock:      (location_id, lot_id) → quantity, version        -- current state
movements:  movement_id (UUID from scanner), lot_id, from_location, to_location, quantity,
            type (receive|transfer|sale|discard|adjust), ts, device_id        -- append-only log

The movements log is the source of truth (every change is an event), and stock is the current state derived from it (updated in the same transaction, or by a consumer).

ArchitectureArchitecture

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
    SC["Scanners / POS - offline capable"] -->|"movement events"| ING["Ingestion API"]
    ING --> K[("Kafka - movements by location")]
    K --> INV["Inventory service - update stock"]
    INV --> DB[("Stock + lots DB")]
    K --> EXP["Expiry monitor"]
    EXP --> AL["Alerts - discount / transfer"]
    OMS["Orders / replenishment"] --> ALLOC["FEFO allocator"]
    ALLOC --> DB
    DB --> DASH["Dashboards - stock by expiry"]

Deep Dive — Choosing which units to shipDeep dive

There are 400 litres of milk at the store across several deliveries. Which ones go out today decides how much gets thrown away.

Weak

Take any available units

Treat the stock as a single quantity and decrement it.

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  STOCK["Milk: 400 units - one number"] --> ORD["Order for 50"]
  ORD --> DEC["quantity = 350"]
  DEC --> BLIND["No idea which units left"]
  BLIND --> OLD["The batch expiring tomorrow is still on the shelf"]
  OLD --> WASTE["Written off, while fresher stock was shipped"]

Quantity alone cannot express expiry, so the system has no way to prefer older stock. Waste is the default outcome, and it is invisible until the write-off report.

Good

Track lots and ship the oldest first

Model stock as lots with a received date, and allocate first-in-first-out.

Much better: the oldest stock moves first and waste drops sharply. But receipt order is a proxy for expiry, not the same thing — a lot received later can easily expire sooner, depending on the supplier and the cold chain. FIFO gets this wrong precisely for the items that matter.

Best

FEFO, against the delivery date

Allocate by first-expired-first-out, and check the expiry against when the customer will actually receive it:

Architecture diagram
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
  ORD["Order at a location"] --> SORT["Lots sorted by expiry_date ascending"]
  SORT --> FILT{"Expiry after the delivery date + minimum shelf life?"}
  FILT -->|"no"| SKIP["Skip - it would arrive unsellable"]
  FILT -->|"yes"| RES["Reserve with a conditional update: quantity >= requested"]
  RES --> NEXT["Short? Take the next lot"]
  SCAN["Scan event - client-generated UUID"] --> IDEM["Idempotent: safe to resend after an offline period"]
  IDEM --> MOVE["Decrement source lot, increment destination - never below zero"]
  • Sort by expiry, not by receipt. It is the same shape of algorithm and it optimises the thing that actually causes waste.
  • Minimum remaining shelf life is the part people miss. A lot expiring in two days should not be allocated to an order delivering in three, and a customer expects more than a day of usable life — so the filter is against the delivery date plus a threshold, not against today.
  • Reserve with a conditional update (quantity >= requested), so two concurrent orders cannot allocate the same units.
  • Scan events carry a client-generated UUID, which makes them idempotent — essential when handheld scanners go offline in a warehouse and resend their queue later.

An expiry-sorted index over lots makes both the allocation and the daily sweep for soon-to-expire stock the same cheap query — and that sweep is what drives markdowns and transfers, which is where the remaining waste is recovered.

Scale and ReliabilityScale

  • Partition movements by location, so events for one location stay in order.
  • Offline scanners queue events locally with timestamps and sync later. The service orders by event time within the location.
  • Cold chain: attach temperature sensor readings to trucks and lots. If the temperature goes out of range, mark affected lots as at-risk.
  • Stock views per region are cached for dashboards, while allocation reads the strongly consistent DB.

Wrap-UpWrap-up

Track stock by lot (with expiry) and location, with an append-only movements log as the source of truth and a current stock table updated idempotently from scanner events (UUIDs make offline resends safe). Allocate orders FEFO with conditional reservations, run expiry monitoring that triggers markdowns, transfers or discards before spoilage, reconcile with physical counts, and flag cold-chain temperature breaches on the affected lots.

More Case Studies

Frequently Asked Questions

What is the Perishable-Goods Inventory and Location Tracking system design question?

Perishable-Goods Inventory and Location Tracking is a system design interview question asked at FAANG companies. It covers e-commerce, databases, event driven, real-time and tests your ability to design scalable, production-ready systems. InterviewSkool's breakdown walks you through requirements, API design, architecture, and trade-offs.

Which companies ask the Perishable-Goods Inventory and Location Tracking question?

Amazon have reportedly asked variations of this question in system design interviews. The exact wording may differ, but the core design challenges remain the same.

How should I prepare for the Perishable-Goods Inventory and Location Tracking interview question?

Start with the problem statement and scale estimates, then design the high-level architecture. Focus on the core components, data model, and API design. InterviewSkool's breakdown covers the full solution with mermaid diagrams and trade-off analysis to help you prep efficiently.

What level is the Perishable-Goods Inventory and Location Tracking question?

This question is suitable for SDE-2, SDE-3, and Staff engineer interviews. The level guidance on this page provides specific tips for each level — SDE-2 candidates should focus on core architecture, while Staff engineers should discuss trade-offs, monitoring, and incremental rollouts.

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with InterviewSkool's AI interviewer.

Start System Design Interview →