System-design interview · Extended interviews
Design ecommerce checkout and inventory reservation
Design checkout so concurrent buyers cannot oversell stock, partial reservations can be recovered, and orders ship only after all items are allocated and payment capture succeeds.
You will learn to
- State a stock invariant across reservation, allocation, shipment, and return.
- Trace all-or-nothing checkout across independently owned inventory records.
- Recover timeouts and compensation using durable step identities.
Practice in this chapter
8 interview questions with model answers and follow-ups.
Go to interview practiceUseful foundations: Databases, data models, and ACID transactions · Message queues, event logs, delivery guarantees, and backpressure · Data partitioning and sharding · Caching: cache hits, misses, write policies and invalidation
Workload and timing examples are interview assumptions.
Dotted concept links open the relevant explanation in a new tab.
01Problem and scope
Checkout reserves stock and arranges payment without overselling. A cart expresses what the customer wants; a hold claims stock temporarily, an allocation keeps it for an order, and shipping consumes it. Warehouse W1 has three sellable mugs M9. Two checkouts each request two, so only one can reserve them before replenishment. Cart C7 also requests notebook N2, letting us trace a checkout where one line succeeds and another fails.
Define available = onHand − reserved − allocated. OnHand is sellable physical inventory; reserved belongs to incomplete checkouts; allocated belongs to confirmed unshipped orders. Keep all quantities nonnegative. This equation lets the interviewer check every state transition rather than trusting a vague “stock service.”
A compensation is a new action that repairs a partially completed checkout, such as releasing a hold; it cannot erase work another service already committed. Payment has separate steps too: authorization reserves spending capacity, capture collects against that authorization, and a void or refund resolves the appropriate stage. These distinctions determine whether the order should wait, confirm, or begin cleanup.
I ask whether an order may partially succeed and whether overselling is acceptable. We choose physical goods, no backorders, and a checkout that either eventually confirms all requested lines or exposes a failed/compensating state. The user may see pending while independent warehouses respond. “All or nothing” describes the eventual customer outcome, not an imaginary transaction spanning every service.
I also distinguish browsing, holding, confirming, paying, and shipping. Client A can add a mug to a cart without reserving it. A short-lived hold protects checkout. An allocation protects a confirmed but unshipped line. Shipping consumes physical stock. Each action has its own durable identity so retries do not create more stock or more business actions.
02Functional requirements
- Edit cart. Version changes; server validates quantities.
- Request checkout. Stable request key returns one pending order.
- Confirm quote. The customer accepts the saved amount, currency, address and terms.
- Reserve all lines. Each line records an authoritative hold result.
- Complete checkout. All lines allocated and payment authorized.
- Fulfill. Payment capture is confirmed before shipment dispatch.
- Cancel before shipping. Allocations release and payment compensation is tracked.
Scope and acceptance boundaries
Support physical items, multiple lines, warehouse selection, no backorders, expiring reservations, and all-or-nothing checkout acceptance. Freeze server-calculated item prices, currency, tax, discounts, and shipping terms before the customer accepts. Split shipment after confirmation is allowed; marketplace settlement and arbitrary partial checkout success are outside scope.
A cart does not hold inventory indefinitely. Checkout is a durable workflow that may temporarily be pending while several services respond. Fulfillment ships allocations after confirmation. Canceling a shipped order requires a return/refund process, not simply releasing an old hold.
Order confirmation and payment capture remain distinct states. A confirmed order can be payment-capture-pending, but it cannot be sent to the warehouse for shipping until capture is known successful. A customer-facing summary explains that state rather than showing a final shipped promise prematurely.
A quote has a validity deadline and version. If tax, discount eligibility, or shipping price changes before acceptance, client A receives a new quote to accept. The server never trusts cart-supplied price fields or silently charges a revised amount under the old request key.
03Non-functional requirements
- Workload. Assume one million orders/day, 1,000 orders/s at peak and five lines per peak order.
- Latency. Target checkout admission p95 below 300 ms and ordinary workflow completion within three seconds while dependencies are healthy.
- Availability. Target 99.9% monthly checkout admission availability. Unknown payment outcomes may remain pending beyond three seconds; this does not justify treating them as failed.
- Stock freshness. Product-page stock may lag by five seconds. Authoritative holds cannot use that cache to approve stock.
- Reservation lifetime. Holds expire after an assumed ten minutes unless a bounded, explicitly authorized extension succeeds. The inventory owner uses its authoritative time and serializes expiry with allocation.
- Durability. Accepted order/inventory transitions survive one zone failure in their respective authoritative stores.
- Partition behavior. A warehouse without write authority refuses new reservations rather than overselling its last unit.
Inventory and fulfillment invariants
- Nonnegative stock. Available, reserved and allocated stay nonnegative; each logical line transition changes quantities once. Clock precision and scheduler delay affect prompt release, not this safety rule.
- Complete confirmation. A confirmed order has every required allocation.
- Authorized dispatch. A shipping command exists only after confirmed payment capture and fulfillment authorization.
- Unknown capture keeps its claim. Retain allocations under an escalation policy while capture is uncertain. A general TTL must not release stock already allocated to a possibly paid order.
04Capacity estimates
| Quantity | Worked assumption | Consequence |
|---|---|---|
| Average orders | 1M/day / 86,400 ≈ 11.6/s | Averages hide flash sales |
| Peak reservations | 1,000 orders/s × 5 lines = 5,000 line operations/s | Size each inventory owner |
| Cart payload | 10M carts × 2 KB = 20 GB | Cache/expire inactive carts |
| Inventory payload | 10M SKU/warehouse rows × 96 B = 960 MB | Before indexes and replicas |
One popular mug can receive a large fraction of those operations. Spreading unrelated SKUs helps aggregate capacity but does not let several replicas sell the same last unit. Test contention and define maximum checkout/admission concurrency for a hot SKU.
At five lines per order, successful checkout performs at least a hold and an allocation transition for each line: roughly 10,000 line transitions/s at peak before releases, shipping, replenishment, and retries. A 10% retry rate with proper identity reuse adds reads and conflict checks but must not add another 10% of sold stock.
If ten-minute holds were admitted continuously at 1,000 orders/s, as many as 600,000 checkout workflows and three million line holds could be active. At 200 bytes of hold metadata, that is 600 MB of raw hold records before indexes and replicas. Those holds also prevent other buyers from purchasing the stock. Limit admissions and holds per user to protect inventory from hoarding as well as overload.
Assume an order and its durable step history average 4 KB. One million orders/day then produces 4 GB/day, or 1.46 TB/year before indexes and replicas, excluding financial retention and document attachments. Long-lived history can move out of the active workflow index after terminal completion, while current pending and compensating orders remain cheap to scan.
The busiest SKU limits throughput because its stock updates must take turns. If 40% of peak orders request M9, its owner sees hundreds of contending mutations per second even when ten million other rows are idle. Measure lock wait and reject or queue excess checkout attempts before a database pileup.
05APIs and contracts
The customer follows one order while its steps finish. K4 identifies the checkout request; each hold and workflow step has its own retry identity. Keeping both lets a browser retry find the same order while workers recover unfinished reservations.
| Operation/record | Example |
|---|---|
| Checkout | POST /checkouts {key:K4,cart:C7,cartVersion:5,quoteId:Q8,quoteVersion:2,addressId:A2} |
| Order line | O401,M9,qty=2,unitPriceMinor=1995,currency=USD |
| Initial stock | M9/W1: onHand=3,reserved=0,allocated=0,version=7 |
| Reservation | H21,O401,M9/W1,qty=2,state=held,expiresAt=... |
| Workflow step | O401,reserveMugs,attempt=1,state=succeeded,result=H21 |
Within one inventory transaction, verify enough available units, insert a unique reservation key, and add its quantity to reserved. Repeated calls for that reservation return the existing result. Store explicit deadlines and terminal states; do not implement release as an unguarded stock increment.
The create response is 202 {orderId:"O401",status:"pending",statusUrl:"/orders/O401"}. Repeating K4 with the same cart version and quote returns O401; different parameters under K4 return 409. Insufficient inventory is a known business rejection, while a downstream timeout yields pending/unknown step status until recovery resolves it.
Inventory APIs accept a caller-scoped immutable operation key: reserve(O401,line1,qty=2,deadline=T), allocate(H21), release(H21), and ship(allocationId,shipmentId). Every response includes the terminal or current reservation state. A retry does not choose a new hold ID merely because the previous response was lost.
Order reads return frozen line prices, quote version, payment state, fulfillment state, and a customer-safe explanation of pending work. History pagination uses order creation time and order ID with an upper boundary. Internal worker endpoints require service identity; the browser cannot directly turn a held reservation into an allocation or claim payment success.
06Data model and access patterns
The cart records what the shopper wants; inventory records who holds the units; workflow records show what still needs to happen. An outbox is a table of messages or actions recorded in the same database transaction as the state change that requires them. A worker can deliver those intents later, so a process crash cannot leave a committed order change with no recorded next step.
| Record | Key and material fields | Authority |
|---|---|---|
| Cart | customer, cartId; version and requested lines | Mutable shopping intent |
| Quote | quoteId, version, amount, currency, validUntil | Server-calculated accepted commercial terms |
| Order | orderId; customer, frozen lines, workflow state | Customer-visible workflow |
| Inventory | SKU, warehouse; onHand, reserved, allocated, version | Stock arithmetic |
| Hold | orderId, lineId; quantity, deadline, state | One line's reservation lifecycle |
| Workflow step | orderId, stepName; request identity and result | Retry and compensation progress |
| Payment attempt | orderId, attemptId; provider reference and outcome | Payment service’s recorded outcome |
| Outbox | eventId; order/line version and intended effect | Saved action that a worker can retry |
Each inventory owner commits its stock row and holds together. The order owner separately commits the order and its workflow steps. They may use different shards, so committing the order cannot atomically update every warehouse.
A due-hold index supports expiration by (warehouse,deadline,state). A pending-step index drives recovery without scanning historical successful orders. Product-page stock availability is derived from inventory events and may lag. Warehouse work lists are also derived views. The inventory owner checks shipment identity so shipping consumes its allocation only once.
Replenishment and inspected returns carry unique source-event IDs. Increasing onHand on a repeated warehouse receipt is just as dangerous as decrementing stock twice on shipping. The same local transaction records the receipt identity and applies its quantity.
07Basic working design
The minimal working design stores orders, stock, holds, and request keys in one database. Client A submits K4 for cart C7/version 5. The API first claims the customer-scoped request key inside the transaction. A concurrent identical retry waits for that claim and returns the committed O401 result; a different payload conflicts. For a new request, it verifies the accepted quote, locks relevant inventory rows in a stable order, checks inventory availability for every line, inserts O401 and its holds, increments reserved, records the request result, and commits. If any line lacks stock, the whole transaction rolls back.
For M9, the committed state becomes onHand 3, reserved 2, allocated 0. Client B's later transaction sees available 1 and cannot reserve two. Both browsers may still show three units because the browsing cache is advisory; checkout is where the definitive decision occurs.
Payment remains external even in this baseline. A durable worker performs authorization using O401's payment attempt identity, records the outcome, and converts holds to allocations before confirming. Network calls never hold inventory row locks. Waiting for a slow payment provider while holding a stock lock would make every competing buyer wait too.
This single-database design keeps the complete multi-line inventory decision in one transaction. It commits all lines together and keeps recovery simple for a modest business. We split ownership only when warehouse autonomy, data volume, or contention provides a concrete reason.
The database decides whether all cart lines can be held; payment remains an external effect.
Read each connection in order
- syncSubmit K4 / cart revision 5Checkout clients → Checkout application
- syncAtomic order and line holdsCheckout application → Orders + stock + holds + outbox
- syncRead intent / save outcomePayment workflow worker → Orders + stock + holds + outbox
- syncStable authorization attemptPayment workflow worker → Payment provider
08Find the baseline flaws
Suppose M9's hot row receives 400 competing checkout attempts/s and each transaction holds its lock for 20 ms. That row can serialize at most roughly 50 such transactions/s before other work, so queue delay grows rapidly. Holding the lock through a 500 ms payment call would reduce the simple ceiling to about two/s. More API servers would make the queue longer rather than increase available stock decisions.
Now consider an unsafe read-then-write implementation: client A and client B both read available 3, both decide that two units fit, and both add two to reserved without an atomic condition. Reserved becomes 4 while onHand remains 3. The inventory equation immediately exposes the violation. Cached stock or independent replica writes do not solve it.
A different failure appears after the system splits warehouses. M9 is held successfully, but N2's service times out. The order coordinator cannot roll back M9 by rolling back its own transaction. It must determine whether N2 was held and then either complete the workflow or release known holds. Blindly starting a fresh checkout or releasing all stock can conflict with a payment that already succeeded remotely.
Finally, expiry can race allocation. A background sweeper that decrements reserved without checking the hold state may release a hold already converted to allocated. State and quantity changes must be one local transaction.
09Improve the design, step by step
First, make workflow intent durable. The trigger is a crash between order creation and payment or inventory calls. The order owner stores named steps and an outbox with the pending order. Workers retry each step under stable identities and record returned results. A crash can no longer erase the next step, but customers may see pending work and workers must schedule recovery. A synchronous single-database transaction remains preferable for the stock portion while all rows share one owner.
Second, assign inventory owners by SKU and warehouse. The trigger is independent warehouse operation and aggregate write load. Each owner commits stock, hold and transition records together, making competing updates take turns. The order coordinator calls those owners. Different stock pools now scale independently, at the cost of a saga across lines and partial temporary reservations. We reject unrestricted active-active writes to one physical pool. Assign separate stock budgets to regions if they must sell while disconnected, accepting that one may run out while another has unused units.
Third, protect flash-sale stock with admission. The trigger is hot-row lock wait exceeding the checkout objective. A bounded per-SKU admission queue limits outstanding contenders and applies per-customer quotas. Requests that cannot meet the deadline are rejected before taking holds elsewhere. This improves useful throughput and prevents a retry storm, but users may wait or be refused even while other SKUs remain fast. Splitting physical stock into independently owned buckets is an alternative only when its allocation and rebalance policy is explicit.
Fourth, separate read projections and fulfillment. The trigger is browsing and order-history traffic competing with mutation work. Product availability, customer history, and warehouse work lists consume versioned events and can be rebuilt. Shipping commands are released only after the order authority records complete allocation and successful capture. This lowers read load and decouples warehouses, but projections lag and duplicated events require version checks. Direct authoritative reads remain necessary for checkout approval and disputed current order status.
Each step adds an operational cost because a previous concrete limit demanded it. The stock equation and stable line identities survive every topology change.
10Detailed architecture
Order workflow and stock owners
An authenticated checkout API validates customer, cart revision, quote, and admission budget. It creates or retrieves the order on the order-owner shard. The workflow engine reads saved steps and calls the relevant inventory or payment service. It does not keep the only copy of workflow state in a process or queue.
Inventory owners partition SKU/warehouse pools. Their replicated stores own the stock row, holds, allocations, unique transition keys, and expiration decisions. Each owner offers a local atomic transition; it does not promise a transaction with other owners. Expiry workers call those same transitions rather than editing counters independently.
Payment and dispatch authority
The payment adapter owns provider attempt identities and uncertain outcomes. It authorizes before confirmation and captures before fulfillment. A capture timeout keeps fulfillment blocked and allocations retained until reconciliation or explicit compensation. The order’s outbox feeds read projections and a fulfillment gate. That gate atomically checks current allocations and capture, commits an irreversible dispatch authorization, and creates the unique shipment intent under the order’s cancellation boundary. It does not merely read eligibility and make a later unchecked warehouse call.
Serving boundaries and implementation
Browsing reads use a cache and product projection, while current checkout decisions route to authorities. Order events, reconciliation, expiry, and shipping are asynchronous. The customer first receives a saved pending order, then polls or receives its final confirmation later. Replicas provide each owner's local durability, but no arrow in the design represents a hidden distributed database transaction.
Start with PostgreSQL row locking or conditional updates for stock, holds and source-event receipts. Keep multi-line work in one transaction while those rows share the database. If warehouse ownership genuinely separates them, persist the saga in the order store or use a durable workflow engine such as Temporal, keeping external step identities and compensation rules explicit. A workflow engine persists progress; it cannot turn a payment provider or warehouse into a participant in the order database’s transaction.
No arrow implies a transaction across warehouses; shipping is gated after allocation and captured payment.
Read each connection in order
- syncAdvisory browsingShopping clients → Catalog / availability projection
- sync1. Checkout K4Shopping clients → Checkout auth + admission
- sync2. Create pending O401Checkout auth + admission → Order authority + steps + outbox
- syncRead / advance durable stepsDurable workflow workers → Order authority + steps + outbox
- sync3. Hold / allocate H21Durable workflow workers → Mug inventory owner
- syncAtomic state + stock arithmeticMug inventory owner → M9/W1 stock + holds
- syncHold / allocate H22Durable workflow workers → Notebook inventory owner
- syncAtomic state + stock arithmeticNotebook inventory owner → N2 stock + holds
- syncExpire only held reservationsGuarded expiry workers → Mug inventory owner
- syncExpire only held reservationsGuarded expiry workers → Notebook inventory owner
- sync4. Authorize / capture / reconcileDurable workflow workers → Payment authority / adapter
- syncStable financial effect identityPayment authority / adapter → Payment provider
- asyncRequest dispatch authorizationOrder authority + steps + outbox → Fulfillment eligibility gate
- sync5. Commit guarded dispatch + shipment intentFulfillment eligibility gate → Order authority + steps + outbox
- sync6. Idempotent shipment commandFulfillment eligibility gate → Warehouse shipment system
- syncApply shipment identityWarehouse shipment system → Mug inventory owner
- syncApply shipment identityWarehouse shipment system → Notebook inventory owner
11Write path and acknowledgement
Each inventory owner checks available stock or the existing hold before changing it. The order workflow saves progress across owners; it cannot commit all their databases in one transaction.
- K4 creates pending O401 once; the coordinator validates C7/version 5 and freezes quoted prices.
- Reserve two M9 at W1 as H21: reserved becomes 2 and available becomes
3−2−0=1. Client B’s request for two fails its stock availability test. - Reserve one N2 as H22 at its chosen owner; record both successful step results durably.
- Authorize payment attempt P8 using O401’s stable identity and amount.
- Convert H21/H22 to allocations under the checkout deadline policy; for mugs, reserved becomes 0, allocated 2, available remains 1.
- Confirm O401 and publish its outbox event. Later, shipping two mugs makes onHand 1 and allocated 0; available stays 1.
Every operation is keyed so a network retry does not reserve, allocate, or ship the same logical line twice.
- Confirmation means all allocations exist and authorization is known successful. Before dispatching fulfillment, capture the authorized amount using the stable payment operation. If capture times out, mark capture unknown and keep shipping blocked. Reconcile the same provider attempt instead of sending another charge.
- Once capture is known successful, the order authority transaction verifies current allocation outcomes, wins against cancellation, and records an irrevocable dispatch authorization with a unique shipment intent. After that boundary ordinary cancellation cannot free its allocations. The warehouse applies that intent under a shipment ID; a repeated message cannot reduce onHand again.
- If any allocation fails because its hold expired, the order remains compensating. Release prior allocations using guarded transitions, void unused authorization or refund a captured amount through the financial workflow, and only then report a resolved failure. Compensation can be pending and must remain visible.
The request key identifies the customer's checkout. Separate reservation, payment and shipment keys identify the actions needed to complete it. Reusing the order ID everywhere without naming the effect could wrongly merge authorization, capture, and refund operations.
12Read and delivery path
Browsing stock availability is advisory. Checkout status comes from authoritative reservations and workflow state, including pending or compensating operations.
- Client A browses M9 through the catalog cache. The response includes advisory stock availability and does not reserve stock. A stale “in stock” display may lead to a later checkout rejection, which is an acceptable declared behavior.
- After receiving O401, client A polls its status endpoint or receives a notification. The API authenticates ownership and reads the order authority when the latest workflow state matters.
- The response combines durable step results into a customer state: awaiting inventory, awaiting payment, confirmed, capture pending, ready to ship, compensating, canceled, or shipped. It does not infer payment success merely because an HTTP request was sent.
- The line view shows frozen accepted prices and quantities, not today's catalog values. A historical order must remain explainable after a promotion ends.
- Customer order lists use a derived index and cursor. A newly created order can be fetched directly even while its list projection catches up. Versioned events prevent an old pending update from replacing a newer confirmed view.
- A cancel command routes back to the order authority. If a shipment has already crossed its defined dispatch boundary, cancellation becomes a return/refund process rather than an unconditional stock release.
A support view includes step identities and provider references under restricted access, allowing reconciliation without exposing payment tokens or addresses in ordinary application logs.
13Correctness deep dive
Suppose H21 succeeds but N2’s owner rejects H22. A distributed transaction is not implied by calling two APIs. Keep O401 pending/compensating and explicitly release H21. A saga is this sequence of local transactions plus named compensation steps; AWS documents orchestration as one implementation pattern. Saga orchestration.
If release times out, retry H21’s transition from held to released. Only the first successful transition decreases reserved. Save each result before moving on. Reservation deadlines do not by themselves prevent expiry from racing allocation or payment. Both paths must check the shared state and version under the chosen time limits.
The critical race is decided inside the inventory owner, not by a coordinator's earlier clock check. Both paths lock H21 and M9/W1 in the same order and obtain fresh authority time after lock waits:
allocate(H21):
begin; lock hold and inventory; ownerNow = freshAuthorityTime()
if state == allocated: return saved allocation
require state == held and ownerNow < expiresAt
reserved -= 2; allocated += 2; state = allocated
record unique allocation result; commit
expire(H21):
begin; lock hold and inventory; ownerNow = freshAuthorityTime()
if state != held or ownerNow < expiresAt: no change
else: reserved -= 2; state = expired
commit
Making M9 and N2 individually correct does not make both operations succeed together. The coordinator confirms only after both durable allocation results exist. If H22 expires after H21 allocates, it compensates H21 and resolves payment. Fulfillment consumes an order-level authorization produced only after that condition and known capture success. This prevents a partial saga from accidentally shipping client A's mug while the notebook checkout is being unwound.
A partition can leave stock temporarily unavailable, but it must not be sold twice. Check the recorded outcome and release it safely; a timeout alone does not make those units available again.
Allocation and expiry both lock the hold and stock row, then check the hold's current state; expiry cannot release units already allocated.
Read each connection in order
- syncAllocate H21 before deadlineOrder workflow → Inventory owner
- syncLock H21 / M9; verify heldInventory owner → Hold and stock database
- syncreserved 2→0; allocated 0→2Inventory owner → Hold and stock database
- returnCommit H21 allocatedHold and stock database → Inventory owner
- syncExpire H21 at deadlineExpiry worker → Inventory owner
- syncLock and inspect H21Inventory owner → Hold and stock database
- returnState allocated: no expiry changeHold and stock database → Inventory owner
- returnNo-op; available remains 1Inventory owner → Expiry worker
- returnReturn saved allocationInventory owner → Order workflow
14Failure and recovery
| Failure or race | Required response and boundary |
|---|---|
| Payment succeeds; response lost | Payment P8 succeeds remotely, but the reply is lost. Do not immediately call a new charge or release all inventory as if payment failed. Preserve the unknown state, reconcile using the same provider attempt identity, and complete or compensate under the workflow’s deadline policy. Detailed financial accounting belongs to the payment subsystem. |
| Hold succeeds; response lost | At t0, the order creates H21. At t1, its worker crashes before saving the response. The replacement retries the same line key and retrieves H21; it does not reserve another two mugs. If the hold has since expired, that terminal result is returned and the coordinator follows compensation rather than silently reviving it. |
| Warehouse partition | During a warehouse partition, reachable owners may have created partial holds. The workflow stays pending until its deadline policy resolves those steps; bounded compensation releases successful holds when the order cannot complete. A payment capture of unknown outcome blocks shipping and automatic allocation release until the financial subsystem reconciles or an operator records a supported resolution. |
| Flash-sale overload | During flash-sale overload, reject or queue new admissions before acquiring partial stock. Existing workflows receive a reserved recovery budget so a flood of new buyers cannot starve releases, capture reconciliation, and completion. This is important: protecting only new checkout latency can leave real inventory trapped in old pending orders. |
| Cancellation races dispatch | If a confirmed allocation must be canceled, the order owner first proves it has not committed dispatch authorization, or obtains a definitive warehouse no-dispatch result under the fulfillment cancellation protocol. The inventory owner also verifies that no shipment transition has consumed the allocation. A request racing shipment selects one guarded state transition, after which the customer receives either canceled or return-required. A warehouse may have started packing or dispatching before its status update arrives; the order state must represent that uncertainty rather than assume cancellation succeeded. |
15Operations, security, and cost
Only trusted services may submit stock movements, and each movement has a unique source identity. Validate quantities as positive bounded integers and money as server-calculated minor units with explicit currency. Tokenize payment details, minimize stored addresses, and restrict support tools. Per-account and per-device reservation quotas discourage bots from immobilizing stock without purchase intent.
The primary safety check continuously reconciles available against onHand minus reserved minus allocated and compares aggregate hold/allocation records with their counters. Operational indicators include oldest pending workflow, reservation age, capture uncertainty, compensation backlog, hot-SKU lock wait, and duplicate shipment suppression. Checkout p95 alone can look healthy while inventory remains stranded.
Test crashes after each remote step succeeds but before the worker saves that result locally. Race expiry against allocation, cancel against shipment, and a provider timeout against reconciliation. Restore drills include request keys, financial attempt references, workflow steps, and outbox state; restoring only orders can recreate side effects on replay.
At peak, ten minutes of holds can create three million line reservations; the number of units held also depends on each line’s quantity. Shortening the hold window to five minutes halves that theoretical inventory exposure but increases payment and customer timeout failures. Measure completion-time tails before choosing the deadline. Database capacity is not the only cost: lost sellable inventory during unresolved workflows is often more important than the bytes storing those workflows.
16Decision ledger and limitations
| Design | Benefit | Cost |
|---|---|---|
| One transaction for all stock rows | All lines commit together | Owners share its scaling limits |
| Reservation saga | Independent warehouses/services | Pending states and compensation |
| Regional inventory budgets | Local decisions during partitions | Stranded units and rebalance complexity |
| Cached stock approval | Fast apparent checkout | Unsafe without authoritative validation |
Use row locks or atomic conditional updates at each owner, with short transactions and retries for conflicts. PostgreSQL concurrency documentation. Regions cannot independently reserve the same inventory copy; allocate disjoint stock budgets or route to one authority. Replenishments and inspected returns need unique source-event IDs so replay cannot invent stock.
We retain a single transaction domain while it fits because it gives the simplest multi-line stock decision. A saga becomes justified when independent warehouse ownership is real. Warehouses can operate independently, but their updates no longer commit together. Orders may remain pending or compensating, tying up stock longer and requiring more recovery work.
Cached stock availability serves browsing cheaply but cannot approve the last unit. Regional budgets allow local decisions during a partition only by assigning disjoint units in advance, with the risk of one region selling out while another has unused stock. A global stock owner uses capacity more efficiently but adds cross-region latency or refusal during a partition.
The remaining bottleneck is a scarce hot SKU and any external payment uncertainty. Neither disappears by adding queues. The next change should follow measured lock contention, hold occupancy, and reconciliation age, with product agreement on waiting and rejection behavior.
17Interview closing
“Browsing stock availability is advisory; the authoritative inventory owner admits only reservations that keep onHand minus reserved minus allocated nonnegative. I start with one database transaction while ownership allows it. At larger scale, each warehouse stock pool owns short atomic transitions, and a durable order workflow coordinates holds, authorization, allocation, capture, and fulfillment through stable effect identities.
“The hard race is expiry against allocation. Both lock the hold and stock row, check the current state and owner time, and change counters together. One wins; the other sees a terminal state. Across warehouses there is no hidden global transaction, so partial progress stays pending or compensating. Confirmation requires all allocations and authorization, and shipping waits for known capture success.
“The costs are temporary stock occupancy and recovery complexity. A timeout is unknown, especially for payment, so I reconcile the same attempt rather than creating a new charge or freeing possibly paid inventory. I would measure hot-SKU lock wait and oldest compensation age, then test every crash between a remote success and its saved local result.”
If the interviewer requires offline regional checkout, I would allocate disjoint regional stock budgets and explain stranded inventory. If backorders become acceptable, I would change the product contract and stock states explicitly rather than quietly allowing negative available stock.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
An inventory row starts with onHand=3, reserved=0 and allocated=0. One checkout reserves two units. What does available mean after reservation and confirmation?
Reveal a model answer
Using available=onHand−reserved−allocated, the row becomes onHand=3, reserved=2, allocated=0, so available is one. Client B cannot reserve two. When client A confirms, reserved moves to allocated without increasing available; when the shipment leaves, onHand and allocated decrease together.
Interviewer follow-up
Why distinguish reserved and allocated?
Reveal the follow-up answer
Reservations can expire while confirmed orders must remain allocated until shipment or an explicit cancellation process. Combining them hides which business transition may release the units.
What the answer must demonstrate: Apply the equation through each lifecycle stage.
A multi-item checkout reserves mugs but fails to reserve its notebook. What state and recovery should the customer observe?
Reveal a model answer
The order remains pending or compensating while the coordinator resolves the failed line and releases the successful hold. It reports a resolved failure only after the required compensation is known complete; it does not silently place a partial order.
Interviewer follow-up
What if compensation itself times out?
Reveal the follow-up answer
Keep compensation pending and retry the same reservation identity. The owner releases a hold only if its current state permits it; if cancellation arrives before a delayed reserve, a retained canceled identity prevents that late reserve from creating a new hold.
What the answer must demonstrate: A saga’s compensation is real work that can fail.
Why not read available and decrement it in another request?
Reveal a model answer
Two buyers can both read available 3 and each decide to reserve 2, overselling four units. I combine the stock availability test, reservation insertion, and quantity update in one owner transaction or equivalent atomic operation. Its result determines whether checkout may proceed.
Interviewer follow-up
Does optimistic locking change the guarantee?
Reveal the follow-up answer
Yes, if the update requires the expected version, the caller checks that a row changed, and conflicts are retried correctly. The stock rule stays the same; the cost of competing updates changes.
What the answer must demonstrate: Name the complete atomic operation.
A payment operation may have succeeded, but checkout lost its reply. May the coordinator create a fresh payment operation?
Reveal a model answer
No. The outcome is unknown, so I query or retry the same provider operation under its idempotency contract. A fresh operation could charge twice. Inventory and order transitions remain recoverable while reconciliation determines whether to complete or compensate.
Interviewer follow-up
What if the stock deadline expires during reconciliation?
Reveal the follow-up answer
If inventory is only held, its bounded deadline may expire; any later financial success then requires the documented compensation policy. Once stock is allocated and capture is unknown, a hold TTL must not release it. Keep shipping blocked and reconcile or escalate before an explicit cancellation releases the allocations.
What the answer must demonstrate: Unknown financial state must not be rewritten as failure.
Can both regions accept orders for the last mug while disconnected?
Reveal a model answer
Not if both believe they own the same unit. I would route reservations to one stock authority or preallocate disjoint regional budgets. During a partition, a region can sell only its budget and stops when that is exhausted, even if stock is stranded elsewhere.
Interviewer follow-up
How do you transfer a unit between budgets safely?
Reveal the follow-up answer
The transfer needs a durable ownership change that never makes the same unit spendable in both regions. Unacknowledged transfers remain unavailable or require reconciliation.
What the answer must demonstrate: Local availability has an inventory-allocation cost.
Why not reserve every item as soon as it enters a cart?
Reveal a model answer
Abandoned carts would immobilize inventory and make hoarding cheap. I treat a cart as intent, then create bounded reservations during checkout when the customer accepts actual commercial terms. If the product wants cart holds, it must explicitly pay that capacity and abuse-control cost.
Interviewer follow-up
Can a client submit its own cheaper price?
Reveal the follow-up answer
No. The server recalculates and freezes the price/tax/shipping snapshot with a version. The client confirms that quote; its submitted total is not authoritative.
What the answer must demonstrate: Explain which service approves inventory and which service calculates and freezes the accepted price.
An expiry worker and checkout concurrently act on the same held reservation. Why can reserved stock not be decremented twice?
Reveal a model answer
Both transitions lock the same hold and inventory row and require state held. Allocation changes held to allocated while moving reserved to allocated; expiry changes held to expired while reducing reserved. The second transaction observes the new state and cannot repeat the decrement.
Interviewer follow-up
What if one warehouse allocates and another expires?
Reveal the follow-up answer
The order remains unconfirmed and compensates the allocated line. Local correctness does not imply a global transaction; fulfillment is gated on all allocations and known successful capture.
What the answer must demonstrate: Identify the exact local state predicate and transaction boundary.
All lines are allocated, but payment capture times out. Can the order ship or the stock be released?
Reveal a model answer
Neither action follows from the timeout alone. I mark capture unknown, block shipment, retain the allocations under a bounded escalation policy, and reconcile using the same provider attempt identity. Releasing immediately might resell stock already paid for; shipping might fulfill an unpaid order.
Interviewer follow-up
What if the provider remains unavailable for hours?
Reveal the follow-up answer
The order stays explicitly delayed and enters an operational review policy. A later cancellation needs a reconciled void/refund outcome, not a guessed failure. The product must accept this availability cost to preserve money and stock correctness.
What the answer must demonstrate: Unknown is a durable state, not a synonym for failed.
Blank-page exercise · 45 minutes
Build the answer yourself
Build client A’s checkout for two mugs and a notebook. Compete with client B for the last units, fail the notebook step, and then repeat with an uncertain payment.
- Write the stock equation and test every state change.
- Trace O401/H21/H22 with frozen prices.
- Make reservation and release idempotent.
- Explain the transaction boundary and saga compensation.
- Handle regional budgets, payment uncertainty, and shipment.
Check that each component and design decision follows from your requirements and workload.
Recall the key ideas
Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.
Design ecommerce checkout and inventory reservationDoes a cart reserve stock?Recall first, then reveal
Not in this design; it expresses purchase intent. Checkout asks the inventory service that owns those units to create reservations with an expiry deadline.
Cart asks; reservation claims.
Return to lessonDesign ecommerce checkout and inventory reservationWhat is a saga?Recall first, then reveal
A saved sequence of local steps, with defined repair actions when a later step fails.
Record each completed step and its compensating action.
Return to lessonDesign ecommerce checkout and inventory reservationWhy is release idempotent?Recall first, then reveal
A retried release must remove a reservation once, not add the same stock back repeatedly.
One reservation, one terminal transition.
Return to lessonFinal revision
Summary and interview notes
Each inventory owner updates stock and reservation state together. A saved order workflow coordinates inventory and payment services, recording confirmation, capture and dispatch separately. Stable operation IDs let workers retry safely; compensation repairs steps already completed when another step fails.
Remember these points
- Available equals onHand minus reserved minus allocated; apply every quantity change with its guarded state transition.
- A cart and cached product availability cannot reserve the last unit.
- The hold owner makes expiry and allocation take turns; only one can remove the reserved quantity.
- Retain canceled line identities so delayed reserve commands cannot recreate abandoned holds.
- Commit dispatch authorization against cancellation before external shipment; unresolved capture blocks dispatch.
Interview tips
- Apply the stock equation through hold, allocation, cancellation, shipment and return.
- Test lost reserve replies and cancellation arriving before the original request, not only duplicate releases.
- Distinguish one-database atomic checkout from a cross-warehouse saga before selecting infrastructure.
Important qualifications
- A saga exposes partial progress and compensation; it does not supply global isolation.
- Physical shipment and payment can have uncertain outcomes that require reconciliation, not guessed failure.
Technical references
- AWS saga orchestration patternDefines orchestration and compensation for workflows spanning independent transaction boundaries.
- PostgreSQL explicit lockingPrimary reference for owner-local concurrency control and conflict handling.
Practice marks stay in this browser.