The Bundle That Sold Twelve Units the Store Did Not Have
The merchant's bundle paired a popular item with a scarce accessory. On a launch morning, twelve orders arrived for the bundle, each consuming one accessory, and the store had nine. Every order was accepted, because at the moment each one checked, the accessory count looked sufficient. The checks were all correct and all stale, which is the signature of derived inventory racing.
A bundle's availability is computed from its components' stock. Computation is a read, and reads do not reserve anything. Two orders reading the same count and both proceeding is the classic check then act race from reviewing locks and shared state, wearing a merchandising costume.
This was observed on a Shopify store using a bundle app that computed availability at render and at checkout validation, checked in Chrome 133.
Why derived inventory oversells
The bundle has no stock of its own. Its sellability is a function: the minimum over components of floor(component stock divided by units per bundle). That function is evaluated at some moment, and the answer is used as if it were a reservation.
Between the evaluation and the actual decrement of component stock, other orders decrement the same components. If the bundle check and the component decrement are not one atomic step, the window admits overselling. Under normal traffic the window rarely matters. On a launch, with concurrent buyers and scarce components, it matters on nearly every order.
The deeper issue is that the bundle layer and the inventory layer are two systems agreeing by polling each other, and agreement by polling is always eventually wrong under concurrency. This is the same shape as the cart race condition at the storefront level, and of shopify inventory oversell debugging for single items.
Where the drift shows
At render. The product page computes bundle availability from a cached or freshly read component count. By the time the customer checks out, the count has moved. The page says available and the checkout says not, or worse, both say available and the fulfilment side discovers the truth.
At validation. A well built bundle app revalidates at checkout. But if the validation is a read followed by a separate decrement, the race remains. Only a validation that atomically reserves the components closes it.
At returns and edits. The reverse direction drifts too. A returned bundle restocks components, and an edited order changes the component mix, and any layer that caches bundle stock must reconcile those events or it will show availability for stock that is committed elsewhere.
The fixes
Reserve, do not compute. The component decrement must be the reservation. At checkout, decrement component stock atomically as part of accepting the order, and reject the order if any component decrement fails. The bundle's availability then is whatever the inventory layer will actually grant, and the race collapses to the inventory layer's own concurrency handling, which is the one place it is enforced.
Bound the optimistic surface. Showing "available" on the product page from a recent read is fine as merchandising, as long as the truth is enforced at the reservation. The defect is not the optimistic display. It is any layer that treats its own optimistic answer as the decision.
Reconcile on every stock event. Component stock changes for many reasons beyond bundle orders: single item sales, returns, adjustments, transfers. The bundle availability cache must invalidate on all of them, or it will drift in both directions. Webhooks or polled deltas both work, provided the event set is complete, and the incomplete event set is the usual defect.
Cap the exposure for launches. For scarce components on a launch, the honest tool is a per customer limit and a visible remaining count that is conservative, rounded down and refreshed frequently. It does not remove the race, but it bounds the oversell to a number the merchant can absorb with an apology instead of a fraud review.
The test that catches it
Simulate concurrent bundle orders against a component stock one higher than the number of orders, and assert that accepted orders never exceed the stock. This is the isolation test from the cache key bugs, applied to inventory: two buyers, one unit, exactly one winner. If the bundle layer accepts both, the reservation is not atomic, and the test fails in seconds what a launch would demonstrate in minutes.
The rule
A bundle is a view over component stock, and a view cannot enforce anything. Enforcement lives only where the stock is decremented, so the bundle's acceptance must be that decrement, performed atomically, with every other availability signal labelled as optimism.
Derived availability that is treated as a decision is how a store sells stock it does not have, politely, concurrently, and at scale. The single item version of the same race is shopify inventory oversell debugging, and the fix in both is the same: the reservation is the source of truth, and everything else is a guess with good manners.
The merchandising temptation
It is worth naming why the wrong shape keeps getting built. Derived availability is seductive because it makes the bundle look always in stock, which converts better, and the oversell only surfaces at fulfilment, far from the product page, where it reads as an operations problem rather than a merchandising decision. So the incentive is to show availability and apologise later, and the apology cost is real but invisible at the moment the choice is made. The honest alternative, showing the bundle unavailable whenever any component is tight, converts slightly less and never sells a unit that does not exist, and over a year the trust retained is worth more than the conversions bought. The race is a technical defect, but the reason it ships is a commercial one, and fixing only the code leaves the incentive intact to rebuild it.
The rule
A bundle is a view over component stock, and a view cannot enforce anything.