Summary
This postmortem analyzes a warehouse data-processing failure where a FIFO (First‑In, First‑Out) matching algorithm between ship_in_df and ship_out_df produced incorrect or incomplete results. The issue stemmed from an overly simplistic join strategy that ignored inventory depletion order, leading to mismatched cost attribution and inconsistent outbound records.
Root Cause
- The system attempted to match outbound shipments to inbound lots using row-wise joins, not FIFO logic.
- No mechanism existed to track remaining quantity per inbound lot.
- Outbound rows consumed quantities that did not exist or were assigned to later inbound batches.
- The implementation lacked a cumulative depletion algorithm, which FIFO requires.
Why This Happens in Real Systems
- FIFO is deceptively simple but requires stateful consumption of inventory.
- Engineers often assume a join or merge can solve the problem, but FIFO requires:
- Iterative matching
- Quantity subtraction
- Carry‑over of remaining inventory
- Dataframes are not inherently stateful, so naïve vectorized solutions fail.
Real-World Impact
- Incorrect cost-of-goods-sold (COGS) calculations
- Financial misstatements due to wrong unit-cost attribution
- Inventory discrepancies between physical and system quantities
- Audit failures when FIFO rules are contractually required
Example or Code (if necessary and relevant)
Below is a correct FIFO implementation in R using tidyverse.
The code produces a ship_out_df_with_ship_in_info table where each outbound shipment is matched to one or more inbound lots according to FIFO.
library(tidyverse)
fifo_match <- function(ship_in, ship_out) {
ship_in % mutate(remaining = qty)
results <- list()
for (i in seq_len(nrow(ship_out))) {
need <- ship_out$qty[i]
out_date <- ship_out$date[i]
for (j in seq_len(nrow(ship_in))) {
if (need == 0) break
available 0) {
take <- min(available, need)
ship_in$remaining[j] <- available - take
need <- need - take
results[[length(results) + 1]] <- tibble(
ship_out_date = out_date,
ship_out_qty = ship_out$qty[i],
ship_in_date = ship_in$date[j],
vendor = ship_in$vendor[j],
qty_used = take,
unit_cost = ship_in$unit_cost[j]
)
}
}
}
bind_rows(results)
}
ship_in_df <- data.frame(
date=c('2025-1-1','2025-1-2','2025-1-10'),
vendor=c("A","B","C"),
qty=c(10,20,30),
unit_cost=c(5,6,8)
)
ship_out_df <- data.frame(
date=c('2025-1-2','2025-1-8','2025-1-12'),
qty=c(8,21,26)
)
fifo_result <- fifo_match(ship_in_df, ship_out_df)
fifo_result
How Senior Engineers Fix It
- Implement iterative depletion logic rather than joins.
- Maintain remaining quantity state for each inbound lot.
- Use loop‑based or map‑based algorithms when vectorization cannot express the logic.
- Add unit tests for:
- Partial consumption of lots
- Multi-lot consumption for a single outbound row
- Edge cases where outbound > total inbound
Why Juniors Miss It
- They assume joins solve everything in tidyverse.
- They underestimate the need for stateful algorithms in inventory systems.
- FIFO looks like a simple ordering problem, but it is actually a resource allocation problem.
- They rarely consider quantity depletion as a dynamic process.