Learning pathsPricingChecking sign-in…
GUIDED PRACTICE

Cart and Checkout

Turn a mutable shopping cart into one durable order while protecting inventory, payment identity and price agreement under retries.

Turn a mutable shopping cart into one durable order while protecting inventory, payment identity and price agreement under retries. This worked design targets SDE-2; the scale figures below are exercise assumptions, not claims about a named company's implementation.

Clarify the prompt

Ask the interviewerDecision to record
Does adding to a cart reserve stock?Assume only checkout creates a timed reservation.
Can prices change before purchase?Require a quote version and explicit confirmation.
Are partial orders allowed?Assume all lines reserve atomically within one warehouse.

Requirements and scope

The core deliverable is: Turn a mutable shopping cart into one durable order while protecting inventory, payment identity and price agreement under retries. The first design implements the APIs below and the state constraints in the data model. Agree the latency, freshness, retention and failure policy before adding infrastructure. Every successful acknowledgment must have an explicit meaning: accepted intent, committed state or completed external work. These are separate milestones.

Capacity and bottleneck reasoning

Assume 10 million daily shoppers, 20 cart writes each and one million checkouts/day: about 2,315 cart writes/s and 12 checkouts/s average. A 100x promotion burst makes inventory hot keys and payment capacity more important than averages.

Keep average and peak demand separate. Add replication, indexes, retries and safety headroom to raw arithmetic before selecting capacity. Load-test the busiest ownership key and recovery traffic as well as uniform happy-path throughput.

APIs and data model

  • PUT /carts/{id}/lines/{sku} {qty,version} -> updated cart
  • POST /checkouts {cartId,cartVersion,quoteVersion,requestKey} -> order or 409
  • GET /orders/{id} -> pending,confirmed,cancelled or requires-action
Contract / pseudocode
cart(cart_id PK,user_id,version); line(cart_id,sku,qty)
order(order_id PK,checkout_key UNIQUE,quote_version,status)
reservation(order_id,sku,qty,expires_at,state); stock(sku,on_hand,reserved)
payment(order_id UNIQUE,provider_key,status)

Authorize resource ownership at the API boundary; do not trust IDs or state supplied by a client. Bind retry identity to the caller and payload for mutating operations. Reject an incompatible replay instead of returning an unrelated stored result.

Component relationships

Cart and Checkout: directed relationships and authoritative boundaries
Cart and Checkout: state boundaries
Read the diagram

Follow the labeled relationship panels; they are separate directed paths, not one global timeline.

Open original
Cart and Checkout: state boundaries
Cart and Checkout: directed relationships and authoritative boundaries

Cart and Checkout: state boundaries

Step-by-step design

1. Establish the authority

Cart storage prioritizes usability; cart quantities are not promises of stock. At checkout snapshot the accepted lines, shipping and price version into an immutable order and bind the request key to its payload.

2. Make retries and concurrent work safe

Reserve every SKU through an authoritative conditional transaction. Lock in SKU order to avoid deadlocks. Persist reservation state and an outbox workflow event together, then authorize payment using one provider identity.

3. Scale and recover the critical path

Confirm only after payment and still-valid reservations agree. Expiry and confirmation compete through conditional state transitions. If payment succeeds after stock is irreversibly released, record compensation/refund work and expose a recoverable order state.

Worked request and concurrency example

Two buyers check out the final unit. Only one reservation can increment reserved while on_hand-reserved >= 1. The winner starts payment; the other sees a stock conflict without being charged. If the winner retries POST after losing the response, the checkout key resolves to the existing order. An expired hold cannot be revived merely by a late webhook: the workflow checks the current order and reservation generation before confirming.

Failure and recovery walkthrough

Payment acceptance is uncertain after a timeout. Keep the order pending, reconcile with the provider and prevent a second payment identity. Compensation itself can fail; persist refund state and an operator-visible recovery queue.

Cart and Checkout: one concrete failure or state-transition sequence
Cart and Checkout: concrete trace
Read the diagram

This numbered trace follows one scenario; explain the durable evidence at each transition.

Open original
Cart and Checkout: concrete trace
Cart and Checkout: one concrete failure or state-transition sequence

Cart and Checkout: concrete trace

Deep dive: an order is a durable agreement, not the cart

In this original exercise, a cart asks for SKU A quantity 1 and SKU B quantity 2. Stock has A=1 and B=2. Prices, tax, currency, shipping and accepted line versions become an immutable order quote. A client changing the cart later cannot mutate an in-progress payment. Scope checkout identity by tenant/customer and compare a canonical quote fingerprint on retry.

RecordAuthority and transition
order Oaccepted → reserving → payment_pending → confirmed, or compensation/repair path
reservation H per SKUactive → consumed or released; one winning terminal transition
payment Prequested → authorized/captured/failed, with unknown as an evidence state
expiry workMay release only its matching active reservation generation

Reserve all local lines together

When both stock rows share a transaction, acquire them in sorted SKU order, validate available quantities and create the order's unique reservation lines in the same transaction. If B is unavailable, roll back A's reservation too. The transaction does not call a provider.

Contract / pseudocode
begin
  lock stock rows for [A, B] in stable order
  require stock.on_hand - stock.reserved >= requested for every line
  create unique reservation(order_id, sku, quantity, state=active, generation)
  increase reserved for every line
  insert order workflow outbox event
commit

For each SKU, define available = on_hand − reserved. Reserving lowers available without changing on_hand. Consuming lowers both on_hand and reserved; releasing lowers reserved only. A negative available or a double release is an invariant violation. An audit history plus idempotent transitions lets you rebuild these totals.

Make expiry and confirmation one conflict decision

Suppose P is authorized near H's expiry. Under this policy, confirmation succeeds only while the authoritative reservation remains active and eligible at database time. A bounded payment-in-progress extension may be a separate explicit transition. A worker cannot extend a hold just because it started before expiry.

Checkout race: payment request P times out; expiry releases the active hold; another order claims the stock; P later succeeds; the original order enters compensation and cannot consume the replacement hold
Late payment evidence must be reconciled with current reservation ownership.
Read the diagram

Payment P times out near hold expiry → Expiry releases the active hold → Another order reserves the same stock → P later reports captured funds → Old order compensates; new hold remains intact

Open original
Cart and Checkout: recovery decision
Checkout race: payment request P times out; expiry releases the active hold; another order claims the stock; P later succeeds; the original order enters compensation and cannot consume the replacement hold

Late payment evidence must be reconciled with current reservation ownership.

If confirmation wins the conditional terminal transition, expiry becomes a no-op. If expiry wins and another order reserves the unit, a late payment success cannot reclaim it. Persist compensation with a stable key: void an authorization or refund captured funds according to the actual provider state. Compensation may time out too; the UI shows pending resolution rather than a fabricated successful purchase.

Cross-shard reservations expose an intermediate state

If A and B are owned by independent shards, do not pretend the local transaction spans them. The coordinator records each leg's stable identity and progress. Reserve A, then B; if B definitively rejects, release A using A's reservation identity. If B's reply is lost, first resolve B rather than issuing a fresh reservation. The order is pending until every required leg agrees.

This saga allows temporary stock withholding. Put an overall deadline and bounded TTL/renewal policy on it. Only consume when all legs are eligible under the chosen protocol. If irreversible partial consumption is allowed, define partial fulfillment and compensation explicitly. Otherwise keep the atomic consumption boundary within one authority or introduce a protocol whose blocking/failure behavior is justified. Writing “saga” on two independent commits does not make them all-or-nothing.

BoundaryDurable evidenceRecovery
A reserved; coordinator crashesA has O/A identityQuery O/A and resume
B rejectedRecorded rejectionRelease matching active O/A once
P timed outSame P identity, unknown resultQuery/retry P under provider contract
P captured after H releasedCapture evidence plus current hold stateRefund/repair; never decrement a new holder's stock
Refund reply lostRefund identity and unknown outcomeReconcile; do not create a fresh refund

Protect the scarce decision from browsing load

Assume 10,000 checkout attempts/s and a measured safe reservation capacity of 2,000/s, including a reserved budget for confirm/expire/repair. At least 8,000/s must be rejected, delayed under a declared queue policy, or prevented upstream. A cached stock display cannot grant a reservation. Per-customer limits, bounded waiting rooms and hot-SKU isolation reduce wasted contention, but the stock predicate still chooses the winner.

Replay and reconciliation consume capacity too. Track active reservations, expiry lag, unknown payment value/age, refund age, admission rejection and quote conflicts. Avoid deleting reservation evidence while late callbacks or replays remain possible. An operator repair records identity and reason; it does not edit stock totals without a movement.

Checkout assessment: prove conservation

Question: A=1 and B=0, but an order requests one of each in a local atomic transaction. May A remain reserved after the operation fails?

Show answer and explanation

Answer: No under this all-lines contract. Roll back every local reservation. Cross-shard partial reservation requires an explicit pending/compensation protocol instead.

Question: A duplicate expiry message arrives after H was consumed. Should it increment available stock?

Show answer and explanation

Answer: No. Release is allowed only from the matching active generation. A consumed or already released hold produces no second stock effect.

Question: A late successful charge arrives after the last unit belongs to O2. May O1 become confirmed anyway?

Show answer and explanation

Answer: No. Its reservation is no longer eligible. Persist the proper void/refund/repair path and retain both payment and ownership evidence.

Score one point for immutable quote identity, local all-line atomicity, stock equations, expiry/confirm arbitration and durable compensation. Then change B to another shard and identify the extra pending states and recovery owner.

Continue with Outbox, Inbox, Sagas and Reconciliation and Transactions and Isolation. Provider-specific retry behavior must follow its documented operation lifetime; see Stripe idempotent requests as one concrete API reference, not a universal provider guarantee.

Decisions and alternatives

DecisionTrade-off
Alternative 1A single-warehouse transaction simplifies all-or-nothing stock reservation. Cross-warehouse orders need an explicit saga and a policy for partial allocation.
Alternative 2Long holds reduce checkout failure but invite hoarding. Short holds improve availability while increasing expiry races and the need for clear UI timers.

Operational contract

Monitor reservation age, checkout conversion, stock conflicts, unknown payment age and compensation backlog. Limit reservations per user, protect prices against client edits and rehearse a provider outage during a last-unit race.

Choose alert thresholds from the agreed user-facing objective. Assign an owner for stuck work and a replay/repair procedure that preserves the original business identity. Bound queue depth, concurrency and recovery throughput so an outage does not produce an uncontrolled catch-up burst.

Foundation, senior and staff depth

  • Foundation: Explain immutable quote, local all-line reservation and stock equations.
  • Senior: Resolve expiry versus confirmation and late payment with stable identities.
  • Staff: Compare cross-shard pending holds with a single-authority design and budget compensation recovery.

Practice before revealing the answer

Spend 45 minutes: 5 clarifying scope, 7 on capacity/APIs/state, 12 on the first architecture, 15 on the concurrency and failure examples, and 6 on trade-offs and operations. Draw your own design before comparing it with this chapter.

Question: Why is a distributed lock around checkout alone insufficient?

Show answer and explanation

Answer: A lease can expire while an old worker continues. Durable stock predicates, unique checkout/payment identities and versioned state transitions must enforce correctness even if the lock holder is stale.

Verification and rollout

Race expiry, confirmation and duplicate release for every hold generation. Check available = on_hand - reserved, all-line local rollback and no consumption of another order's hold. Exercise refund uncertainty and cross-shard partial reservation; canary admission below measured stock/provider limits.

Primary technical reference

Official documentation for the mechanism used in this exercise. This lesson is an original interview design, not a description of undisclosed company internals.

Before this design, review these mechanisms and identify which owns the critical state transition:

Use the Interview Playbook to adapt the depth and timing, then return to the question breakdown overview.

Shared foundation: Transactions and Isolation

Transactions and Isolation: Predict write skew, enforce a shared invariant and retry whole transactions safely. Work its failure history before extending this design.

Shared foundation: Outbox, Inbox, Sagas and Reconciliation

Outbox, Inbox, Sagas and Reconciliation: Recover publication gaps, duplicate deliveries and uncertain compensation using durable business state. Work its failure history before extending this design.

13:00Self-guided practice timer
The timer resets when you leave this page. Save your design separately.
Your challenge

Why is a distributed lock around checkout alone insufficient?

Your design draft

Clarify assumptions, explain your approach, and test the difficult cases. Save your draft, then compare it with the study notes.

Read study notes

Self-review checklist

Self-guided practice. Automated AI feedback and code execution are not connected.