A flash sale oversold three hundred units and the stock ledger never went negative. How is that possible, and where do you look first?
The oversell sits in accepted orders, not in on-hand stock. The checkout gate read a cached availability projection while orders arrived faster than it refreshed, so the promise ledger went negative and the stock ledger never had to. You fix the gate, not the cache.
What the interviewer is scoring
- Does the candidate separate on-hand, reserved and available-to-promise and say which of them the checkout gate consulted
- Whether the arrival rate is compared against the refresh interval rather than the cache simply being called stale
- That a safety buffer is examined for how long it survives at sale arrival rate instead of being offered as the fix
- Whether other demand on the same pool - stores, marketplaces, allocated-but-unpicked orders - is enumerated
- Does the answer say which three hundred customers are disappointed and on what stated basis
Answer
The oversell is in the promise, not in the stock
Nothing about the stock ledger is inconsistent with overselling three hundred units, and understanding why is most of the answer. On-hand is a count of physical units in a location, and it only moves when something physical happens: a receipt, a pick, an adjustment, a return booked in. You cannot pick a unit that is not there, so on-hand is naturally floored at zero. What you oversold was not stock but promises, and promises live in a different place — accepted orders that carry a commitment to supply.
So the failure is a set of orders that were accepted and cannot be allocated. That is a real and measurable quantity, and most estates do not measure it. The single most useful instrument to leave behind after this incident is a count of accepted-but-unallocatable order lines, alerted on in near real time, because it is the only figure that goes wrong at the moment of the mistake rather than hours later when a picker finds an empty location.
What the checkout gate was actually reading
Available-to-promise is not on-hand. It is a projection: on-hand, less units already reserved for other orders, less any safety buffer, plus inbound supply you are willing to commit against, computed per fulfilment location and then aggregated. That projection is expensive relative to a product page render, so it is computed and cached, and every layer between the ledger and the shopper adds latency to the number.
flowchart LR
L[On-hand ledger] --> A[ATP projection]
R[Reservations] --> A
A --> C[Availability cache]
C --> P[Product page]
C --> K[Checkout gate]
C --> M[Marketplace feed]
S[Store POS] --> LThe edge worth noticing is that the checkout gate reads the same cached projection as the product page, while store tills write straight to the ledger — so the one decision that must be correct is served by the slowest copy of the number.
Now put arrival rate against refresh interval. If the projection refreshes every few seconds and a flash sale delivers hundreds of submissions per second, then every submission inside one refresh window sees the same value and each of them believes it is safe. The window does not have to be long to be fatal: it only has to be longer than the time it takes demand to consume the stock. The cache was not broken, and it was not even especially stale. It was consulted by a decision that cannot tolerate any staleness at all, which is a design error rather than an operational one.
Where the rest of the gap comes from
Read-staleness is usually the largest contributor but rarely the only one, and a good answer enumerates the others because each needs its own fix.
Multiple channels drawing on one pool is the classic second cause. Store tills, a marketplace listing, a telephone order desk and a wholesale allocation may all sell from the same physical units, and any channel whose demand reaches the ledger on a delay is invisible while it does. A marketplace feed that publishes a quantity and receives orders against it is committing on your behalf using a number you sent earlier.
Reservation lifecycle is the third. If a reservation is taken at basket rather than at submission it must expire, and if the expiry sweep releases units late you undersell before the sale and oversell during it. If reservations are taken only at submission you have a race between the gate and the write, which is the direct cause when the gate and the ledger are eventually consistent.
Aggregation across locations is the fourth and it produces a subtler failure. A total of three hundred available across twelve stores does not mean an order for three units can be met, and if your sourcing rules will not split a line, the aggregate figure overstates what you can honestly promise. The oversell then appears as orders that cannot be fulfilled even though the arithmetic was correct.
Finally, the on-hand figure itself has error in it from shrinkage, mis-picks and un-booked receipts, which is why the buffer exists at all.
Why the safety buffer did not save you
The reflex fix is to raise the buffer, and it is worth being explicit about why that fails here, because it is the difference between an adequate answer and a good one. A buffer is stock you refuse to sell in order to absorb error in the count and lag in the pipeline. Its protection is measured in units, but the exposure during a flash sale is measured in units per second. A buffer that comfortably covers a normal day's mis-pick rate is consumed inside a single refresh window when demand arrives two orders of magnitude faster, so it converts a large oversell into a slightly smaller oversell while permanently costing you margin on every ordinary day. Buffers are the right instrument for count error and the wrong instrument for concurrency.
The fix has to make the gate authoritative for the scarce line. The general shape is to serve availability from cache everywhere it is only informational, and to require a single atomic decrement against a durable counter at the moment of acceptance, with the order refused if the decrement fails. For a genuinely hot line, the sale becomes a token allocation: you mint exactly as many purchase entitlements as you have units, hand them out atomically, and let checkout proceed only against a held token. That turns an unbounded race into a queue, and it also gives you a truthful sold-out state you can latch at the edge rather than recomputing under load. Tokens then need their own expiry, and that expiry is a conversion-versus-oversell dial someone commercial should own.
Then decide who does not get one
The technical fix does not resolve the incident. Three hundred customers hold a confirmation, and possibly a charge, for something that does not exist, and the interviewer will notice if you stop before that. You need a stated basis for choosing who is honoured — acceptance timestamp is the defensible one, and it is worth having decided in advance rather than in the moment. Everyone else needs a prompt, honest cancellation with the money released rather than left held, because a silent order that ages for a week does far more damage than a same-day apology. Where money was captured rather than only authorised, the refund path and the timing you can promise are part of the answer.
Keep the two failures separate in the post-incident work as well. The oversell is a design fault in the gate. The three hundred disappointments are a customer-service event with a cost, and quantifying that cost is what funds fixing the gate properly instead of raising the buffer again.
On-hand cannot go negative, so it will never tell you that you have oversold. Instrument accepted-but-unallocatable lines, and make the acceptance decision for a scarce line an atomic write rather than a read of a projection.
Likely follow-ups
- You issue a fixed number of purchase tokens up front. What happens to the ones held by shoppers who abandon?
- Cycle counting later shows the on-hand figure was wrong before the sale even opened. Does that change your design?
- How would you keep a single hot SKU's counter from becoming the bottleneck for the whole sale?
- What is the alert that would have told you at minute two rather than at the morning stand-up?
Related questions
- Two customers try to buy the last item at the same time. How do you handle inventory?hardAlso on oversell and available-to-promise6 min
- A three-line order could be sourced from either of two warehouses. How do you decide where it ships from, and what happens when one line falls through?hardAlso on allocation6 min
- What happens to a block trade between execution and clearing, and why does a firm need a middle office to do it?mediumAlso on allocation4 min
- A hot path allocates heavily and garbage collection is showing up in your profile. What does Span give you that a substring does not?hardAlso on allocation4 min
- A maintenance script deletes rows it should not have touched, and nobody notices for six hours. Walk me through the recovery.hardSame kind of round: scenario6 min
- A customer ports their number away to another operator. What has to happen on your side, and what usually goes wrong?hardSame kind of round: scenario6 min
- A customer's API token turns up in a public repository. What do you do in the next hour, and what in your token design decides how bad this is?hardSame kind of round: scenario6 min
- A dependency that normally answers in 80ms starts taking eight seconds. What in your service reacts, and in what order?hardSame kind of round: scenario6 min