Learning pathsPricingChecking sign-in…
← LLD problem library

LLD-019 · DESIGN PRACTICE

Inventory and Order Fulfillment

Separate on-hand, reserved and shipped quantities; make allocation, expiry and partial fulfillment preserve ownership.

Local Java 21 reference verified: 29 checks. Learner drafts are not executed. Author self-review; independent review pending.

Existing introductory lesson · Version 1

Understand the problem

Design a local multi-warehouse inventory service that reserves every order line atomically, supports partial shipments, expires or releases only unshipped allocations and audits stock adjustments. Explain the exact transaction boundary before discussing distributed warehouses or carrier integration.

Before you start

  • Immutable quantity snapshots and integer arithmetic
  • Atomic compound operations and ownership
  • Idempotency fingerprints and optimistic versions
  • Expiry clocks and fulfillment state machines

Scope and explicit decisions

  • One in-memory inventory owner serializes commands. Warehouse/SKU keys and opening quantities are fixed; stock adjustments change existing keys. No database, real warehouse devices, carrier calls, returns, backorders, payment integration or cross-process transaction is implemented.
  • Quantities are nonnegative long integers; requested quantities and TTL are positive. There are no fractional units, lots, serial numbers or unit conversions. checked arithmetic rejects quantity/deadline overflow where applicable. Version/identity counters are finite; exhaustion requires retiring the local instance rather than wrapping counters.
  • reserve receives order identity, SKU demand, TTL and operation key. It plans every line before changing allocations, using warehouse-name order for deterministic selection. This policy is reproducible, not optimal for distance, shipping cost, fairness or number of packages.
  • onHand is physical stock still at a warehouse; reserved is the active unshipped portion; available=onHand-reserved. Reservation.original stays historical, remaining tracks still-held units and shipped tracks locally accepted shipments. Shipped units leave both onHand and reserved together.
  • Time is a trusted nondecreasing caller input. Time-bearing commands/read stock advance authoritative expiry. At now>=expiresAt an ACTIVE reservation expires and releases remaining quantities before a fresh shipment can proceed. get returns the last materialized snapshot and does not advance time.
  • reserve and ship have typed global operation-key fingerprints. Matching retries resolve the same reservation and return its current view, including later shipment/expiry changes; they do not allocate or ship again. Different inputs or operation kinds under the same key reject.
  • A rejected allocation does not partially reserve order lines. Advancing time may still expire older reservations before a new command fails; failure is not a promise that unrelated expiry facts remain frozen.
  • Partial shipment consumes only requested keys/quantities belonging to that reservation. Release/expiry clears remaining allocations only. Already shipped units are never restored by cancellation; returns require a separate inspected receipt and adjustment contract.
  • adjust requires the current stock version, a nonblank reason and a result onHand>=reserved. Allocation, release, shipment and adjustment advance stock versions. Audit records cover adjustments; this is not a complete durable movement journal. Histories and operation keys are retained without compaction.

Worked examples

Input: w1:A=2,w2:A=3,w1:B=1. Reserve A=4,B=1.

Outcome: Allocation takes w1:A=2,w2:A=2,w1:B=1; physical onHand stays unchanged and reserved rises.

Greedy warehouse order is deterministic. All three allocation entries commit together.

Input: Reserve five units, ship two, then release the remaining reservation.

Outcome: onHand fell by two; reserved returns to zero. Only the unshipped three become available again.

Cancellation releases a promise; it is not a return receipt for already shipped goods.

Your design attempt

Each language has a separate draft across all three levels. Saving is device-only and does not sync to your account. Anyone using this browser profile can load it. Loading is always explicit.

Java 21 reference code is provided for local study. Use Run program in the editor to execute your code and test driver. Program runs show output; they do not grade your design.

Solution.java
Run program · Java 21

No unsaved changes · 208/20000

Self-review

Checks apply to this visit. Self-review is not a passed submission or verified mastery.

Try before revealing

Hint 1

Where is each promised unit owned?

Hint 2

Which quantities may cancellation safely release after partial shipping?

Hint 3

What happens at exactly expiresAt, including a retry of an older successful shipment?

Requirements and invariants

R1 — Reserve all requested SKU quantities or reserve none of the new order. reserve protects I1.

R2 — A shipment cannot exceed its reservation remaining quantities or consume twice under a retry key. ship protects I2.

R3 — Release and expiry restore availability only for unshipped units. release protects I3.

R4 — Stock adjustments reject stale intent and cannot invalidate active reservations. adjust protects I1.

I1: For every warehouse/SKU: 0<=reserved<=onHand, and reserved equals the sum of remaining quantities on ACTIVE reservations.

I2: For each allocation while ACTIVE, original=remaining+shipped; a successful shipment reduces onHand and reserved equally and advances shipped exactly once per operation identity.

I3: COMPLETED, RELEASED and EXPIRED reservations hold no remaining units. Release/expiry never adds already shipped units back to onHand.

Derive the design

A SKU counter cannot explain ownership

A single SKU→quantity map can prevent some oversells but cannot answer which warehouse owns a promise, which order may release it, or whether cancelled units already shipped. Add warehouse/SKU stock and a reservation allocation map, then derive available stock rather than decrementing physical stock on reservation. Plan every order line before committing, because compensating a partially applied local reservation is needlessly harder than validating it first.

Alternative: A database implementation can lock selected stock rows in a stable order or use version-conditional updates inside one transaction. That preserves local allocation semantics; independent warehouse services require a saga/partial-reservation contract instead of claiming one monitor spans processes.

Define units and warehouse ownership

Clarify integer quantities and whether onHand includes reservations. Write available=onHand-reserved and tie every reserved unit to an ACTIVE reservation.

Choose the allocation transaction boundary

Resolve retry identity and expiry, prepare every SKU allocation, then commit holds once. This avoids exposing a partially accepted local order.

Model partial progress explicitly

Original is historical; remaining is still held; shipped is accepted outbound progress. Terminal release clears remaining and does not pretend that original=remaining+shipped after released units have left the reservation.

Separate physical and local evidence

ship records a trusted local shipment fact. Real pick/pack/carrier execution requires external command identity, unknown outcomes, outbox/inbox and reconciliation; a local counter is not proof that a truck departed.

Responsibilities and ownership

Inventory

Owns: Stock, active allocation accounting, operation identities, clock and adjustment history

Methods: reserve, ship, release, adjust, stock, get, invariantHolds

Local transaction owner; no physical shipment or database I/O.

Key / Stock

Owns: Warehouse/SKU identity and quantity/version snapshot

Methods: Validated records and snapshot accessors

SKU totals do not replace warehouse ownership.

Reservation

Owns: Historical original, remaining, shipped, expiry, version and state

Methods: Immutable copied-map accessors

Snapshot is evidence, not a mutable allocation handle.

ReserveRequest / ShipRequest

Owns: Typed command fingerprint and correlated reservation identity

Methods: Private operation lookup

An order ID alone does not make every later shipment the same operation.

Allocation transaction

Warehouse-first order is a policy, not a shipping-cost optimization.

  1. Demand + operation identity
  2. ↓ Trusted monotonic time

    Expire / plan every SKU
  3. ↓ No partial new allocation

    Commit all reservation holds
  4. ↓ Physical stock unchanged

    Immutable current reservation
Partial fulfillment boundary

A partial shipment can leave ACTIVE remaining units; terminal reservations hold none.

ACTIVE remaining allocation

Retain shipped history

Ship subset: onHand and held fall

Never restore shipped units

Release/expire: held only falls

Data model, schema and relationships

SKU describes the item; warehouse_stock owns quantity at one warehouse. An order reservation splits into warehouse allocations, and shipments consume only selected allocated quantities.

Implementation boundary: Local keys/allocation maps implement conservation and current-view retries. Order/SKU/catalog/line tables explain durable fulfillment modeling; real picking, returns and carrier facts remain adapters.

These are proposed logical schemas with field contracts, not executable database migrations. PK identifies a row, FK references an existing parent, UK is a uniqueness rule. A composite PK identifies the combination. Nullable references are marked with ?. A foreign key protects identity; cross-row limits, timing and state transitions also require the transaction rules below.

sku: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

sku relationshipssku 1 to warehouse_stock 0..*: is stocked. warehouse_stock.sku_id → sku.id.is stocked1 parent · 0..* childrenwarehouse_stockid [PK]warehouse_id [FK]sku_id [FK]skuid [PK]name
warehouse: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

warehouse relationshipswarehouse 1 to warehouse_stock 0..*: holds stock. warehouse_stock.warehouse_id → warehouse.id.holds stock1 parent · 0..* childrenwarehouse_stockid [PK]warehouse_id [FK]sku_id [FK]warehouseid [PK]name
warehouse_stock: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

warehouse_stock relationshipswarehouse_stock 1 to allocation 0..*: backs promises. allocation.stock_id → warehouse_stock.id. warehouse_stock 1 to stock_adjustment 0..*: is corrected. stock_adjustment.stock_id → warehouse_stock.id.backs promises1 parent · 0..* childrenallocationid [PK]reservation_id [FK]stock_id [FK]is corrected1 parent · 0..* childrenstock_adjustmentid [PK]stock_id [FK]deltawarehouse_stockid [PK]warehouse_id [FK]sku_id [FK]
fulfillment_order: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

fulfillment_order relationshipsfulfillment_order 1 to inventory_reservation 0..*: requests promises. inventory_reservation.order_id → fulfillment_order.id.requests promises1 parent · 0..* childreninventory_reservationid [PK]order_id [FK]expires_atfulfillment_orderid [PK]status
inventory_reservation: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

inventory_reservation relationshipsinventory_reservation 1 to allocation 0..*: allocates. allocation.reservation_id → inventory_reservation.id.allocates1 parent · 0..* childrenallocationid [PK]reservation_id [FK]stock_id [FK]inventory_reservationid [PK]order_id [FK]expires_at
allocation: relationship map

Connectors name the relationship; multiplicities state parents per child and children per parent. 0..1 means optional, 1 required, 0..* any number. Self-links show two roles of the same entity.

allocation relationshipsallocation 1 to shipment_line 0..*: is shipped. shipment_line.allocation_id → allocation.id.is shipped1 parent · 0..* childrenshipment_lineid [PK]allocation_id [FK]shipment_keyallocationid [PK]reservation_id [FK]stock_id [FK]

Entity definitions and field contracts

sku

Sellable integer-unit item.

FieldType / keyMeaning and reference
idtext · PKSKU identity
nametext · VALUECatalog label

Code mapping: Demand SKU keys.

  • Unit conversion, lots and serial tracking require additional scope.

warehouse

Physical inventory location.

FieldType / keyMeaning and reference
idtext · PKWarehouse identity
nametext · VALUEDisplay label

Code mapping: Key.warehouse.

  • Inventory authority must be explicit per location.

warehouse_stock

Physical quantity and active promises.

FieldType / keyMeaning and reference
iduuid · PKStock-row identity
warehouse_idtext · FKLocation · warehouse.id
sku_idtext · FKItem · sku.id
on_handbigint · VALUEStill physically present
reservedbigint · VALUEActive remaining allocation sum
versionbigint · VALUEAdjustment guard

Code mapping: Stock/held/version maps.

  • Unique(warehouse_id,sku_id); 0<=reserved<=on_hand.

fulfillment_order

Customer demand owner.

FieldType / keyMeaning and reference
idtext · PKOrder identity
statusenum · VALUEBusiness lifecycle

Code mapping: Order identity input.

  • An order may need several reservation attempts; command identity must distinguish them.

inventory_reservation

All-or-nothing promise lifecycle.

FieldType / keyMeaning and reference
iduuid · PKReservation identity
order_idtext · FKDemand owner · fulfillment_order.id
expires_attimestamp · VALUEPromise deadline
stateenum · VALUEACTIVE COMPLETED RELEASED EXPIRED

Code mapping: Reservation.

  • Terminal reservation holds no remaining units.

allocation

Warehouse-specific reserved line.

FieldType / keyMeaning and reference
iduuid · PKAllocation identity
reservation_iduuid · FKPromise owner · inventory_reservation.id
stock_iduuid · FKPhysical source · warehouse_stock.id
originalbigint · VALUEHistorical allocation
remainingbigint · VALUEStill held
shippedbigint · VALUEAccepted outbound quantity

Code mapping: Reservation maps.

  • Unique(reservation_id,stock_id); ACTIVE original=remaining+shipped. Terminal release needs released/expired accounting if full conservation is journaled.

shipment_line

Deduplicated outbound evidence.

FieldType / keyMeaning and reference
iduuid · PKShipment-line identity
allocation_iduuid · FKConsumed promise · allocation.id
shipment_keytext · VALUELogical shipment command
quantitybigint · VALUEAccepted shipped units
request_fingerprintjson · VALUEFull reservation/quantities command meaning must agree for all lines under one key

Code mapping: Ship operation history; full shipment journal extension.

  • Unique(shipment_key,allocation_id) with full typed command fingerprint; quantity cannot exceed owned remaining.

stock_adjustment

Audited physical correction.

FieldType / keyMeaning and reference
iduuid · PKAdjustment identity
stock_iduuid · FKCorrected source · warehouse_stock.id
deltabigint · VALUESigned exact correction
reasontext · VALUEAudit reason
result_versionbigint · VALUEAccepted stock version

Code mapping: Adjustment audit list.

  • Cannot reduce on_hand below reserved; expected version protects stale intent.

Relationship cardinalities and their reasons

Parent → childMultiplicityFK / why
warehouse → warehouse_stock

holds stock

1 parent per child; 0..* children per parentwarehouse_stock.warehouse_id → warehouse.id

One location holds quantities for many SKUs.

sku → warehouse_stock

is stocked

1 parent per child; 0..* children per parentwarehouse_stock.sku_id → sku.id

Warehouse/SKU pair owns physical units.

fulfillment_order → inventory_reservation

requests promises

1 parent per child; 0..* children per parentinventory_reservation.order_id → fulfillment_order.id

A stable operation key determines whether a retry is the same reservation.

inventory_reservation → allocation

allocates

1 parent per child; 0..* children per parentallocation.reservation_id → inventory_reservation.id

One order promise can span SKUs and warehouses atomically in this owner.

warehouse_stock → allocation

backs promises

1 parent per child; 0..* children per parentallocation.stock_id → warehouse_stock.id

Sum of ACTIVE remaining allocations equals reserved.

allocation → shipment_line

is shipped

1 parent per child; 0..* children per parentshipment_line.allocation_id → allocation.id

Partial shipment creates progress without discarding remaining ownership.

warehouse_stock → stock_adjustment

is corrected

1 parent per child; 0..* children per parentstock_adjustment.stock_id → warehouse_stock.id

Adjustments are explicit audited facts, distinct from cancellation.

Transaction boundaries

  1. Reserve: prepare every SKU allocation, lock chosen stock rows in stable order, revalidate availability and commit reservation/all allocations/held deltas atomically.
  2. Ship: resolve command identity, advance expiry, validate all remaining quantities, decrement on_hand and reserved together and store evidence.
  3. Release/expire: clear only ACTIVE remaining quantities. Returns need a new inspected receipt, not resurrection of original allocation.

Indexes and uniqueness

  1. Unique warehouse/SKU and reservation/stock; index ACTIVE reservation(expires_at).
  2. Index allocation(stock_id,reservation_id) for reconciliation; unique scoped shipment operation and fingerprint.

Schema traps and failure cases

  1. Independent warehouse commits cannot claim global all-or-nothing order acceptance without a coordinating transaction/saga contract.
  2. After shipping two of five, cancellation releases three; restoring all five invents stock.

Trace the records through one operation

  1. Order O needs SKU X7; allocation takes W1/X5 and W2/X2.
  2. Shipment consumes W1/X3, leaving W1 on_hand/reserved both lower by three.
  3. Expiry releases only remaining allocations; duplicate shipment key cannot decrement again.

Method contracts

reserve(order,demand,ttl,operationKey,now) → Reservation

Validate immutable demand, resolve a matching retry, expire old allocations, plan all lines, then commit all new holds. Fresh deadline is checked now+ttl; retries retain the original deadline.

ship(reservationId,quantities,operationKey,now) → Reservation

At the exact expiry boundary fresh shipment rejects. Matching retry returns current reservation without another decrement. Validate every requested key/amount before committing any shipment quantity.

release(reservationId,now) → Reservation

ACTIVE becomes RELEASED after clearing remaining holds. Other terminal states return unchanged; shipped stock is not restored.

adjust(key,delta,reason,expectedVersion,now) → Stock

Advance expiry, then check current version and exact result quantity. A stale version or quantity below held stock rejects; accepted adjustment records reason/result version.

stock(key,now); get(reservationId); invariantHolds()

stock advances global expiry; get observes last materialized reservation without a clock advance. invariantHolds independently recomputes active reserved totals from reservations.

What, when, why and how

Each concept is attached to the requirement it protects, its implementation, an alternative and a test.

Quantity ownership and conservation

what
Separate physical stock from promises and accepted shipments.
when
An order may wait, partially ship or cancel after some units leave.
why
One mutable available counter loses the evidence needed to distinguish a promise release from a physical return.
how
Stock holds onHand/reserved. Reservation copies original/remaining/shipped. ship decrements onHand and held equally; release/expiry decrements held for remaining only.

Code: Stock, Reservation, ship, close · Invariants: I1, I2, I3

Alternatives, cost and failure example

Alternative: A full movement ledger can derive these projections with stronger audit/rebuild evidence, at the cost of more storage and transaction design.

Cost: Maps and reservation histories grow; this reference does not retain a complete receipt/shipment movement journal.

Counterexample: Reserve five, ship two, cancel, then incorrectly add five back to onHand.

Test: Check partial shipment then release: only three become available and two remain physically gone.

All-or-nothing allocation with deterministic policy

what
Prepare a complete candidate allocation before changing authoritative holds.
when
One order spans several SKUs and warehouses.
why
Committing the first line before discovering another is unavailable strands stock and creates partial promises.
how
reserve scans sorted warehouse/SKU keys into a temporary allocation, rejects an unsatisfied line, and applies holds only after every SKU is satisfied.

Code: reserve, onHand · Invariants: I1

Alternatives, cost and failure example

Alternative: Warehouse cost optimization is a separate strategy; ordered greedy allocation is enough to make this reference reproducible.

Cost: For D demanded SKUs and W stock keys, scan cost is O(D×W) plus sorting demand; one monitor serializes unrelated inventory operations.

Counterexample: SKU A exists but SKU B is unavailable; a loop reserves A before returning failure.

Test: A multi-SKU failure leaves all new-order holds absent; competing last-unit reservations yield exactly one owner.

Retry identity versus current state

what
Recognize a repeated command before repeating its quantity mutation.
when
A reservation or shipment response is lost and the client retries after later changes.
why
Checking current ACTIVE state first can reject a legitimate old success or applying the same shipment twice can erase stock.
how
Typed ReserveRequest/ShipRequest fingerprints bind operation key to content and result reservation. Matching repeats return current view; changed quantity/type/key meaning rejects.

Code: previous, ReserveRequest, ShipRequest, ship · Invariants: I2

Alternatives, cost and failure example

Alternative: Persist original response snapshots if the API promises byte-for-byte historical replies; current-view retry is an explicit different contract.

Cost: Operation history must survive the retry horizon in a durable system. Process-local retention is neither durable nor bounded.

Counterexample: A successful final shipment becomes COMPLETED; its retry fails ACTIVE validation or decrements stock again.

Test: Repeat a shipment key after completion/expiry, and reject the same key with changed quantities.

Expiry and optimistic adjustment ownership

what
Expiry changes reservation eligibility; stock versions detect stale adjustment intent.
when
Cleanup races shipping or an operator adjusts stock while reservations change.
why
A blind cleanup can release another allocation, and a blind absolute adjustment can consume promised units.
how
at expires only ACTIVE identities with expiresAt<=now. close clears only remaining holds. adjust checks version after expiry and validates onHand>=held before writing an audit record.

Code: at, close, adjust · Invariants: I1, I3

Alternatives, cost and failure example

Alternative: A deadline heap improves expiry lookup; transactional conditional state transitions are needed across multiple workers.

Cost: Current at scans retained reservations, so stock reads/commands are O(history) even when no expiry is due. Caller time must be trustworthy.

Counterexample: Ship at the exact deadline after an unrelated stock read already expired the reservation.

Test: Assert exact-boundary rejection, no resurrection of shipped units and stale-version rejection after stock ownership changes.

Read and run the Java reference

Reveal complete Solution.java

Java 21. Save as Solution.java; compile with javac --release 21. The repository also contains Test.java and the lld:java verification command. This reference implements the local contract above.

Solution.java Java 21
import java.util.*;
/** Local warehouse inventory; physical shipping and durable transactions are external contracts. */
public final class Solution {
    private Solution() {}
    public record Key(String warehouse,String sku){public Key{if(warehouse==null||warehouse.isBlank()||sku==null||sku.isBlank())throw new IllegalArgumentException("stock key");}}
    public enum State { ACTIVE, COMPLETED, RELEASED, EXPIRED }
    public record Reservation(String id,String order,Map<Key,Long> original,Map<Key,Long> remaining,Map<Key,Long> shipped,long expiresAt,int version,State state){public Reservation{original=Map.copyOf(original);remaining=Map.copyOf(remaining);shipped=Map.copyOf(shipped);}}
    public record Stock(long onHand,long reserved,long version){}
    public record Adjustment(Key key,long delta,String reason,long version){}
    private record ReserveRequest(String order,Map<String,Long> demand,long ttl){public ReserveRequest{demand=Map.copyOf(demand);}}
    private record ShipRequest(String reservation,Map<Key,Long> quantities){public ShipRequest{quantities=Map.copyOf(quantities);}}
    private record Operation(Object fingerprint,String reservation){}
    public static final class Inventory {
        private final Map<Key,Long> onHand=new TreeMap<>(Comparator.comparing(Key::warehouse).thenComparing(Key::sku));
        private final Map<Key,Long> held=new HashMap<>(),versions=new HashMap<>();
        private final Map<String,Reservation> reservations=new LinkedHashMap<>();
        private final Map<String,Operation> operations=new HashMap<>();
        private final List<Adjustment> adjustments=new ArrayList<>();
        private long sequence,time;
        public Inventory(Map<Key,Long> initial){if(initial.isEmpty()||initial.values().stream().anyMatch(v->v==null||v<0))throw new IllegalArgumentException("initial stock");onHand.putAll(initial);initial.keySet().forEach(k->{held.put(k,0L);versions.put(k,0L);});}
        private void key(Key k){if(!onHand.containsKey(k))throw new IllegalArgumentException("stock key");}
        private static void id(String s){if(s==null||s.isBlank())throw new IllegalArgumentException("id");}
        private void version(Key k){versions.put(k,Math.incrementExact(versions.get(k)));}
        private void at(long now){if(now<time)throw new IllegalArgumentException("time");time=now;for(var r:new ArrayList<>(reservations.values()))if(r.state()==State.ACTIVE&&r.expiresAt()<=now)close(r,State.EXPIRED);}
        private Reservation close(Reservation r,State state){r.remaining().forEach((k,q)->{held.put(k,held.get(k)-q);version(k);});var closed=new Reservation(r.id(),r.order(),r.original(),Map.of(),r.shipped(),r.expiresAt(),Math.incrementExact(r.version()),state);reservations.put(r.id(),closed);return closed;}
        private Operation previous(String key,Object request){id(key);var op=operations.get(key);if(op!=null&&!op.fingerprint().equals(request))throw new IllegalArgumentException("key reused");return op;}
        public synchronized Reservation reserve(String order,Map<String,Long> demand,long ttl,String operationKey,long now){
            demand=Map.copyOf(demand);id(order);if(demand.isEmpty()||demand.keySet().stream().anyMatch(s->s==null||s.isBlank())||demand.values().stream().anyMatch(q->q==null||q<1)||ttl<1)throw new IllegalArgumentException("demand");
            var request=new ReserveRequest(order,demand,ttl);var old=previous(operationKey,request);if(old!=null){at(now);return get(old.reservation());}long deadline=Math.addExact(now,ttl);at(now);
            var allocation=new LinkedHashMap<Key,Long>();
            for(var item:new TreeMap<>(demand).entrySet()){
                long needed=item.getValue();for(var stock:onHand.entrySet())if(stock.getKey().sku().equals(item.getKey())){long take=Math.min(needed,stock.getValue()-held.get(stock.getKey()));if(take>0){allocation.put(stock.getKey(),take);needed-=take;}if(needed==0)break;}if(needed!=0)throw new IllegalStateException("insufficient stock");
            }
            var r=new Reservation("R"+(++sequence),order,allocation,allocation,Map.of(),deadline,0,State.ACTIVE);allocation.forEach((k,q)->{held.put(k,Math.addExact(held.get(k),q));version(k);});reservations.put(r.id(),r);operations.put(operationKey,new Operation(request,r.id()));return r;
        }
        public synchronized Reservation ship(String reservationId,Map<Key,Long> quantities,String operationKey,long now){
            quantities=Map.copyOf(quantities);if(quantities.isEmpty()||quantities.values().stream().anyMatch(q->q==null||q<1))throw new IllegalArgumentException("shipment");var request=new ShipRequest(reservationId,quantities);var old=previous(operationKey,request);at(now);if(old!=null)return get(old.reservation());var r=get(reservationId);if(r.state()!=State.ACTIVE)throw new IllegalStateException("reservation not active");
            for(var e:quantities.entrySet())if(e.getValue()>r.remaining().getOrDefault(e.getKey(),0L))throw new IllegalStateException("exceeds reservation");
            var remaining=new HashMap<>(r.remaining());var shipped=new HashMap<>(r.shipped());
            quantities.forEach((k,q)->{long left=remaining.get(k)-q;if(left==0)remaining.remove(k);else remaining.put(k,left);shipped.merge(k,q,Math::addExact);});
            quantities.forEach((k,q)->{onHand.put(k,onHand.get(k)-q);held.put(k,held.get(k)-q);version(k);});
            var next=new Reservation(r.id(),r.order(),r.original(),remaining,shipped,r.expiresAt(),Math.incrementExact(r.version()),remaining.isEmpty()?State.COMPLETED:State.ACTIVE);reservations.put(r.id(),next);operations.put(operationKey,new Operation(request,r.id()));return next;
        }
        public synchronized Reservation release(String id,long now){at(now);var r=get(id);if(r.state()!=State.ACTIVE)return r;return close(r,State.RELEASED);}
        public synchronized Stock adjust(Key key,long delta,String reason,long expectedVersion,long now){key(key);id(reason);at(now);if(versions.get(key)!=expectedVersion)throw new IllegalStateException("stale stock version");long next=Math.addExact(onHand.get(key),delta);if(next<held.get(key))throw new IllegalStateException("adjustment violates reservations");onHand.put(key,next);version(key);adjustments.add(new Adjustment(key,delta,reason,versions.get(key)));return stock(key,now);}
        public synchronized Reservation get(String id){var r=reservations.get(id);if(r==null)throw new IllegalArgumentException("reservation");return r;}
        public synchronized Stock stock(Key key,long now){key(key);at(now);return new Stock(onHand.get(key),held.get(key),versions.get(key));}
        public synchronized List<Adjustment> adjustments(){return List.copyOf(adjustments);}
        public synchronized boolean invariantHolds(){var actual=new HashMap<Key,Long>();for(var r:reservations.values())if(r.state()==State.ACTIVE)r.remaining().forEach((k,q)->actual.merge(k,q,Math::addExact));return onHand.keySet().stream().allMatch(k->held.get(k)>=0&&held.get(k)<=onHand.get(k)&&held.get(k).equals(actual.getOrDefault(k,0L)));}
    }
}

reserve separates planning from mutation

The allocation map is temporary until every line can be satisfied. Failed demand cannot leave a new partial reservation behind.

ship validates all entries before decrement

Each quantity must belong to remaining. Copies of remaining/shipped are prepared, then physical and held stock change together under one owner.

close releases remaining only

Expiry and explicit release share this code path. Historical shipped entries survive terminal reservation state.

adjust checks the post-expiry version

Advancing expiry may itself advance the stock version. The adjustment must use current ownership evidence rather than bypassing those changes.

Complexity and limits

  • Let R be retained reservations, W warehouse/SKU keys and D requested SKUs. Time advancement scans O(R), and expiry release additionally visits the remaining allocation entries of expired records.
  • Fresh reserve scans O(D×W), plus O(D log D) demand order and allocation-map updates. Stored TreeMap updates cost O(log W); this is a clarity reference, not a large-warehouse optimizer.
  • ship validates/updates K requested keys and copies reservation allocation maps; account for those copies plus O(K log W) stock writes and the shared expiry scan.
  • Space is O(W+all retained allocations+operation identities+adjustment history). Snapshot copying costs proportional to maps. No compaction, paging, byte quota or independent warehouse parallelism is implemented.

Walk through the actual reference

These walkthrough fixtures are emitted only after the Java harness verifies their operations and expected state. Concurrency tests additionally exercise competing callers.

Multi-warehouse reservation and partial shipment

Step 1 of 3: Reserve x7,y2

w1:x5,y2; w2:x2 held

Invariant: Every line allocated atomically

Read the complete trace table
ActionState after actionInvariant
Reserve x7,y2w1:x5,y2; w2:x2 heldEvery line allocated atomically
Ship w1:x3w1:x onHand2/reserved2Shipment deducts on-hand and reservation together
Repeat shipment keyNo extra deductionShipment identity deduplicates quantity change

Expiry releases only unshipped quantity

Step 1 of 2: Hold expires at t=10 after partial ship

Remaining holds released; shipped3 stays deducted

Invariant: Expiry cannot resurrect physical stock

Read the complete trace table
ActionState after actionInvariant
Hold expires at t=10 after partial shipRemaining holds released; shipped3 stays deductedExpiry cannot resurrect physical stock
Attempt late new shipmentRejected; reconcile externally if physical shipment already happenedLocal reservation authority is explicit

Mistakes and counterexamples

Decrement physical stock on reserve and again on ship

Failing case: One order consumes the same units twice.

Repair: Reserve changes held; ship changes held and onHand together.

Release original allocation after partial shipment

Failing case: Cancellation recreates units already shipped.

Repair: Release remaining quantities only.

Allocate lines incrementally before knowing all can succeed

Failing case: A missing final SKU strands earlier reservations.

Repair: Plan then commit all lines atomically.

Treat inventory retry keys as arbitrary request labels

Failing case: One key is reused for a different shipment quantity.

Repair: Persist typed immutable request fingerprints and reject mismatches.

Engineer → Senior → Staff

Choose a deep dive

Model stock and atomic reservations.

Executed: multi-warehouse allocation, all-line validation, quantity invariants and immutable snapshots.

Change exercise

Add a warehouse preference policy without changing conservation.

Worked answer guidance

Keep allocation planning separate from commit; inject a stable ordered candidate list, validate all demand, then run the same hold transaction. Compare policy quality separately from correctness.

Interview follow-ups

Why keep onHand and reserved?

They represent physical inventory and promises. Deriving available preserves both facts and makes shipment/release rules explicit.

Is deterministic greedy allocation optimal?

No. It is reproducible and simple; cost, distance, split packages and fairness need explicit policy objectives.

Return to the lesson →

Provenance and scope of evidence

Original teaching material. The local contract is implemented by Solution.java and tested by Test.java on Java 21. Distributed persistence, real provider/device adapters and deployment are not claimed.

Reference verification is not independent editorial review, production deployment verification or a claim that distributed adapters are implemented.