Amazon (Marketplace)
This is not the cart-and-checkout problem - that's its own page. This one is one level up: a catalog where the same product can be sold by several sellers at several prices, and an order has to pick a seller and take stock out of that seller's inventory, not some single global count.
Requirements
Functional
- A
CataloglistsProducts; each product can have multipleListings, one perSeller, each with its own price and stock count. - A customer places an
Orderfor a product; the system picks a listing to fulfill it from (cheapest available, in this design) and decrements that seller's stock. - A
Sellercan update the price or stock of their own listings without touching anyone else's. - An order can span multiple products from different sellers and still be one order the customer sees as a whole.
Non-functional
- Picking a listing must never oversell a seller's stock, even when several orders for the same product arrive close together.
- Adding a new way to rank listings (fastest shipping, best seller rating) should not
require changing
OrderorCatalog.
Design
Catalog maps a product to its listings; it never touches money or stock directly. Order
asks a ListingSelectionStrategy to pick a listing per line item, then calls that listing's
reserveStock - the only place stock actually decreases. A Seller owns its listings the
same way ParkingFloor owns its spots: nothing outside touches a Listing's stock field
except through that one method.
- 1The customer orders a product, not a specific seller.
- 2The order asks the catalog for every seller currently offering this product.
- 3Which listing wins is delegated entirely - order placement never hardcodes "cheapest".
- 4Only the chosen listing's stock ever changes; every other seller's count is untouched.
- 5The listing confirms the reservation before the order is finalized.
Splitting Product (the catalog entry: title, description, category) from Listing (one
seller's price and stock for that product) is what makes "three sellers, three prices" a
non-event instead of a special case bolted onto Product.
Class diagram
Code
Design decisions
Listingis a separate class fromProduct, not aMap<Seller, Price>field onProduct. A listing has its own lifecycle - a seller can deactivate theirs while others stay live - and its own stock count that changes independently. Modeling it as a first-class object givesreserveStocksomewhere to live; a bare map would push that logic back up intoOrderorCatalog, whichever touched it first.reserveStocklives onListing, and it's the only mutator of stock in the whole system. Every path that reduces stock - a placed order, a returned order putting it back - goes through this one method, so the invariant "stock never goes negative" has exactly one place to be enforced instead of N.- Listing selection is a
ListingSelectionStrategy, mirroringFeeStrategyfromparking-lot.mdx. Cheapest-first is one policy; a real marketplace also weighs shipping speed, seller rating, and Prime eligibility. None of that should changeOrder's placement logic, only which strategy gets wired in. - What's missing for a real system:
reserveStockhere isn't atomic across concurrent orders - a real implementation needs a compare-and-swap or DB-level row lock on the listing's stock count so two orders racing for the last unit can't both succeed, and a reservation needs a timeout/release path for abandoned carts rather than committing stock the instant a listing is picked.
Common follow-ups
- Two customers order the last unit of the cheapest listing at nearly the same moment -
what actually happens? Both
Order.placecalls can passstrategy.selectand pick the sameListingbefore either callsreserveStock, butreserveStock's check-then-decrement is not atomic against a concurrent caller either. One order needs to win and the other needs to seereserveStockreturnfalseand fall back to the next listing (or fail cleanly) - which meansreserveStockneeds a real lock or a compare-and-swap on the stock count, not just a plainif. - A seller wants to run a flash sale where their own listing is always preferred
regardless of price - how do you support that without touching
Order? Write a newListingSelectionStrategy(say,SellerPromotedFirstStrategy) that checks apromotedflag onListingbefore falling back to cheapest-first.Order.placenever changes - it already delegates the entire "which listing wins" question to whatever strategy it was constructed with. - How would you let a customer return an order and put the stock back? Add a
Listing.restock(qty)call (already on the class) triggered by aReturnobject that references the originalOrderLine. The important constraint: restocking must go through the sameListingthat originally sold the unit, not a fresh lookup by product id, since a seller could have relisted the same product at a different price in the meantime. - What happens if a seller deletes a listing while an order referencing it is still
in flight? Nothing in this design stops it, and it's a real gap worth naming out loud
in an interview:
Listingobjects are held directly byOrderLine, so a delete would need to be a soft "deactivate" (a flag theCheapestFirstStrategyfilters on for new orders) rather than an actual removal, so existingOrderLinereferences stay valid for receipts and returns.
Check yourself
Why is `Listing` a separate class from `Product`, rather than a `Map<Seller, Price>` field on `Product`?