Guide chapters
Stock levels
The problem#
Stock looks like a number attached to a product. It stops being one at the first cart, because between adding to the cart and paying the goods have to be held for somebody, and at the first integration, because the number starts arriving from outside.
A marketplace adds a third complication: stock has an owner. Two sellers can offer the same item and those are two independent stock levels, even if the buyer sees one listing.
Minimum version#
- Stock belongs to a variant and a seller, not to a product.
- A reservation is a separate concept from available quantity, with its own lifetime.
- Availability is quantity minus reservations, computed on read.
- A sale reduces the quantity rather than only releasing the reservation.
What not to build yet#
- Warehouse processes: goods receipt, transfers and stocktaking. The location dimension usually comes with the platform, but operating several of them is separate work.
- Goods receipt, transfers and stocktaking. That is a separate product, not a marketplace module.
- Forecasting and reorder points.
- Reservations distributed across systems. Reserve locally, synchronise the quantity.
Plan ahead#
A location from day one, even a single one#
Stock keyed by variant and location costs one field today, with one location per seller. Adding locations later means migrating every stock level and every reservation, including open ones.
The origin of a value recorded next to it#
Stock can come from a manual edit, an import or a synchronisation. Recording where the current value came from costs one field, and without it the first argument about why stock changed cannot be settled.
Changes as operations, not overwrites#
If stock is only a number overwritten in place, two concurrent orders can pull it apart. Expressing the change as an operation on the value, applied atomically, costs the same and removes a whole class of concurrency problems.
Triggers#
- the first sale of an item that had run out;
- a seller reports stock lower than it should be, with no sales to explain it;
- a seller wants to ship from two places;
- a seller attaches a shop and wants the levels to agree;
- the same goods start selling through two channels at once.
Next stage#
After the first trigger: validation at order creation with a clear message. After the second: a view of stock change history, so the seller question can be answered. After the third: a second location, which is cheap if the model had one from the start. After the fifth: settling whether the platform owns stock or only mirrors it.
Common mistakes#
| Assumption | Why it costs |
|---|---|
| "Stock is a field on the product." | Variants and multiple sellers force a catalogue-wide migration. |
| "A reservation does not need an expiry." | Abandoned carts hold goods, stock drops to zero without sales. |
| "Checking stock at add-to-cart is enough." | Between the cart and the payment the item runs out. |
| "We will just overwrite the number." | Two concurrent orders lose one of the changes. |
| "We will add locations when we need them." | The migration also covers open reservations, which cannot be reconstructed. |
What works in practice#
The pattern that holds for any value changed concurrently is the same: express the write as an operation on the value rather than read, compute, write. An order spanning several sellers produces several simultaneous changes to the same number, and with read-then-write some of them are simply lost. It is a few lines of difference and it removes a problem that appears at random and is hard to reproduce.
The second observation concerns the location dimension itself. It was available from the start, and the cheapest decision turned out to be simply using it rather than flattening stock into a single number on the product. Running several dispatch points as an operational process stayed unnecessary far longer, but the dimension was in place on the day it was needed.
Checklist#
- Stock belongs to a variant and a seller, not a product.
- A reservation is a separate concept and it expires.
- Availability is computed rather than stored as a second number.
- A stock change is an atomic operation, not an overwrite of a read value.
- The model allows a second location even though there is one today.
- It is settled whether overselling is allowed, and where that is checked.
I design multi-vendor platforms with onboarding, payments, moderation, and operational workflows.
Explore marketplace development