InterviewLegend

Design Cart, Checkout & Orders

Frequently asked
Alex Chen46 min readUpdated 835 real interview reports
In this lesson
  • Design the cart-to-checkout-to-order flow at Black Friday scale
  • Use an idempotency key so a retried checkout charges once and makes one order
  • Go deep on the saga, the transactional outbox and the doorbuster waiting room

Understanding the problem

The prompt

Design the buy flow for a retailer operating at national scale: the cart, the checkout and the order behind an online storefront with hundreds of millions of shoppers. A shopper adds, updates and removes items in a cart that has to follow them from phone to laptop to app and survive for days. When they commit to buy, the system prices the cart with tax and promotions, reserves the stock, authorizes payment and records an order. It must never double-charge a card, never lose a cart and place exactly one order even when the shopper mashes the button or a request times out and retries. And it has to hold all of that up on the worst day of the year. A doorbuster drops and a national crowd reaches for the same item and the same checkout button in the same second.

This is the buy flow and it is the most recognizable system a retailer going tech-first is built to run. Every other retail design in this guide, inventory, catalog, fulfillment, hangs off it or feeds it. The cart is where a shopper's intent gathers. Checkout is where that intent turns into money and a commitment to ship. The order is the record the rest of the company acts on. Three parts, one loop. Get this loop right and the storefront makes money reliably. Get it wrong and you either drop carts, double-charge cards or fall over on the one day the volume matters most. The interviewer is not checking whether you can wire three services together. They want to know whether you can make the buy flow land exactly once when parts of it fail.

How hard the round pushes depends on the level. Below senior it is often folded into a low-level design or a coding screen and in one loop it shows up as a ninety-minute machine-coding round where you model the cart cleanly in code. From senior up it becomes a genuine architecture conversation, run by a principal or staff engineer who works with you as a fellow builder rather than an examiner. The prompt starts small and grows under you. First it is one shopper and one cart. Then it is a checkout that has to reserve stock and charge a card without a distributed lock. Then a doorbuster just dropped and the same button is being pressed a thousand times a second. Expect the push to land where a retailer feels it: the double charge, the lost cart and the peak.

The Walmart angle
This is the literal buy flow behind walmart.com, the money path for hundreds of millions of shoppers, so the bar is reliability, not cleverness. The round rewards a design that treats never double-charging and never losing a cart as hard correctness rules, and everything else as negotiable. That is how the storefront is graded in production. An interviewer here has almost certainly operated a piece of checkout or orders, which means a generic three-box answer gets probed fast and a design that names idempotency, compensation and peak degradation lands hard. Skip over-engineering the clever parts. Spend your effort on the boring guarantees: one order per intent, one charge per order, a cart that follows the shopper everywhere. Design for that and every box has a reason a retail engineer will recognize.

Functional requirements

Keep the core small and let the interviewer stretch it. There are five jobs. First, manage the cart: add, update and remove items and keep it persistent across devices and days. Second, price the cart at checkout: line totals, tax and promotions, computed against the live catalog. Third, place the order, which is the crux: reserve the stock, authorize payment and create the order as one all-or-nothing outcome. Fourth, run the order through its life, from placed to paid to fulfilled and fan that out to the rest of the company. Fifth, make the whole place-order path idempotent, so a retry never buys twice. One requirement outweighs the rest. Placing the order safely, exactly once, under failure and retries, is where the design either holds or double-charges.

In scope

  • Cart management: add, update and remove line items, persisted per shopper and merged from a guest cart to a logged-in one on sign-in
  • Checkout pricing: line totals, tax and promotion or coupon application, recomputed against the live catalog rather than trusting a stale cart price
  • Place order as a distributed transaction: reserve inventory, authorize payment and create the order with an all-or-nothing outcome and idempotency on retries
  • The order lifecycle: an explicit state machine from placed to paid to fulfilled to delivered, fanned out to inventory, fulfillment and notification
  • Peak survival: hold the buy flow up under a Black-Friday spike and hot-item contention, degrading browse before ever degrading checkout or payment

Below the line (name it, then move on)

  • The authoritative inventory count and oversell logic: we reserve against it, but the single-owner reservation and availability truth live in the inventory design
  • Product catalog and search: item attributes, descriptions and ranking are a separate system that we read prices and details from at checkout
  • The payment processor internals: card networks, PCI vaulting and settlement live behind a gateway we call idempotently, not inside our services
  • Last-mile delivery and fulfillment routing: which node ships and how it reaches the door is downstream of the order we emit
  • Fraud scoring and returns or refunds: real workflows, but their own systems that subscribe to order events rather than sitting on the checkout path

Non-functional requirements

Reason each target from what the buy flow is for. Correctness leads and it is about money and trust. A card must never be charged twice for one order and an order must be placed exactly once no matter how many times the request arrives. That is the hard line, and it is narrow. Browsing a catalog can be a little stale. A cart total can be recomputed. But the charge and the order cannot be duplicated. Durability comes next and it is unusual for a cart. A cart is not a session that can evaporate. It is state the shopper owns, worth real revenue. It has to survive a server restart, a switch from phone to laptop and days of sitting idle. Then availability under peak, because the buy flow has to stay up on the day it is stressed hardest, and it should bend by shedding browse long before it ever slows a checkout. Latency matters least of the four here, though a sluggish checkout costs conversions, so the read paths lean on cache and the write path stays lean.

The slack worth banking now is that browse and cart reads can be fast and slightly stale and the place-order path is where you spend for truth. A cart badge that is a second out of date is fine. A product page that shows a price the checkout will recompute is fine. What cannot be loose is the reserve-charge-record step, so that is the one path that runs as a carefully-ordered transaction with idempotency threaded through it. We will spend that slack deliberately later: caching cart reads, letting a waiting room meter the crowd and letting the event log absorb a burst rather than shedding it onto the order service.

  • never double-charge a card. The hard money-correctness bar: exactly one payment authorization and capture per order, even when the request is retried three times.
  • never lose a cart. A cart is durable state owned by the shopper, so it survives a server restart, a device switch and days of idle, not a session that can evaporate.
  • exactly one order per intent. A retried place-order returns the same order rather than creating a second one, no matter how the retry arrives.
  • peak holds the buy flow up. On a Black-Friday doorbuster the money path stays whole; the system degrades browse and coarsens stock counts before it ever slows checkout or payment.

Sizing the buy flow

Put rough numbers on the board before the architecture and derive them out loud, because a good interviewer redoes the arithmetic with you. Treat every figure as an illustrative teaching estimate. Start with shoppers. A national storefront serves on the order of a couple hundred million customers a week. On a heavy day, call it tens of millions of active shoppers, and a healthy fraction of them have a cart open at once. That is millions of live carts at any moment, each small, a handful of line items, but each one durable state that has to follow its owner across devices for days. So the first sizing fact is not a rate, it is a population: millions of open carts to keep, most of which never check out.

Now turn shoppers into orders and the retail calendar sets the peak. On an ordinary day orders trickle in at a steady rate. On Black Friday the storefront takes millions of orders across the day and the ugly part is that they do not spread evenly. A doorbuster drops at a set minute and the order rate on that minute jumps to thousands a second while a normal minute sees a few hundred. An order is never one write. Each one fans out into a coordinated sequence: reserve inventory, authorize payment, create the order, then emit events downstream. So thousands of orders a second is really tens of thousands of cross-service steps a second, every one of which has to be safe to retry. Add-to-cart writes run several times the order rate, because most carts never convert, so the cart path takes tens of thousands of writes a second on its own. And cart reads dwarf all of it, since every page paints the cart. The last multiplier is the nastiest. When the site slows under the spike, shoppers and clients retry, so the same order intent can arrive two or three times. Only idempotency keeps that from becoming two or three charges.

  • Live carts: millions open at peak. a cart per active shopper, most never converting, each small but durable and required to follow its owner across phone, app and desktop for days. This is a population to keep, not a rate.
  • Peak order rate: thousands/sec on a doorbuster. a set-minute drop, not a smooth ramp and each order is a multi-step cross-service transaction, so the real step rate is an order of magnitude higher.
  • Hot-item contention: thousands of checkouts on one SKU. a doorbuster funnels the whole crowd at a single item, the exact key the inventory reservation has to serialize to avoid oversell. The concentration, not the aggregate, is the danger.
  • Retry amplification: same intent arrives 2-3x. a slow spike makes clients and shoppers retry, so place-order sees each intent several times. Idempotency collapses the duplicates into one order and one charge.

Two lines in that grid set the agenda. Millions of durable carts have to live somewhere cheap to read and impossible to lose and the place-order step has to survive both the raw peak and the retry storm without ever charging twice. So the design keeps a few things true at once. The cart path stays fast and durable. The checkout path is safe to retry and unwinds cleanly on failure. The order fans out exactly once. And the whole thing bends under peak by sacrificing browse before buying. Each deep dive below is one corner of getting that right.

The set-up

Core entities

Name the objects before drawing anything. Five carry this design and the one to pin down first is the order intent, because it is what the idempotency key names and what a retry has to resolve back to.

1

Cart

A shopper working set of line items, owned by the customer identity rather than a session and persistent across devices and days. It holds intent, which SKUs and how many, not a locked price or a stock guarantee. Millions are open at once and most never convert.

2

CartItem

One line in a cart: a SKU, a quantity and a reference to the item, not a frozen price. It is the unit of add, update and remove. The binding price and tax get computed at checkout against the live catalog, so a CartItem is a pointer to a product, not a contract on it.

3

Checkout

The short-lived transaction that turns a cart into an order: it prices the cart, reserves stock, authorizes payment and creates the order. It carries the idempotency key that makes the whole place-order path safe to retry and it is the saga the deep dive is built around.

4

Order

The durable record of a placed purchase and an explicit state machine: placed, paid, fulfilled, delivered, with cancelled and returned as branches. Once placed it is not overwritten, its state advances by events and it is the record the rest of the company acts on.

5

Payment

The authorize-and-capture record tied to an order and fronted by an idempotency key, so a duplicate request collapses into one charge. Authorize a hold at checkout, capture it when the order is confirmed and void it if the checkout has to unwind.

Pin where each piece lives, because the interviewer will ask. The carts land in a durable store keyed by customer, with a hot copy in cache. The access pattern is a point read and point write by that identity a great many times over. The order intent and the order state live in the order service store, keyed by order id and by the idempotency key. A retry can find an in-progress order instead of starting a new one. The payment lives behind an external gateway, referenced by the order and the key, never in our own tables. And the order events flow through a durable log, which downstream teams fold at their own pace.

The low-level design round asks you to build exactly this model in code. In one loop the ninety-minute machine-coding round hands you a working cart to implement. A Cart that adds, updates and removes items. A CartItem that holds a SKU and quantity. An Order created from a cart, priced with promotions and moved through its states. The bar is clean, extensible object modeling in layers: a data-access layer under a service layer under a thin controller. The states and the pricing rules should be real classes instead of a tangle of conditionals. Design the entities here the way you would defend them there: clear responsibilities, an order that is a state machine and a cart that holds intent rather than a price.

API

Keep the external surface thin and let the shape of each call say which regime it belongs to. Cart calls are frequent, cheap and forgiving. The checkout call prices the cart and hands back a token. The place-order call is the strong one and everything about it exists so a retry lands on one order. Reference these by name in the deep dives.

The interface
RequestFire-and-forget
read or mutate the shopper cart: add, update or remove a line item.
GET /v1/cart · POST /v1/cart/items { sku, qty } · DELETE /v1/cart/items/{sku}
{ cartId, items: [{ sku, qty }] }
point ops by customer identity, served from cache, the highest-volume calls in the flow; a guest cart merges into the logged-in one on sign-in.
start a checkout: price the cart with tax and promotions and mint an idempotency key.
POST /v1/checkout { cartId }
{ checkoutId, idempotencyKey, quote: { subtotal, tax, discounts, total } }
recomputes the binding numbers against the live catalog; the returned key is the token every place-order retry must carry.
place the order, the strong path: reserve stock, authorize payment, create the order.
POST /v1/orders { checkoutId, idempotencyKey, paymentMethod }
{ orderId, state } or 409 out-of-stock / payment-declined
idempotent by the key: a retry returns the same order, never a second one. This is where a double charge is won or lost.
read order status, backed by the order state machine.
GET /v1/orders/{id}
{ orderId, state, timeline: [...] }
a point read of the current state; the shopper polls it after placing and support reads it to see how a charge resolved.
Order serviceDownstream consumerspublish order lifecycle events to inventory, fulfillment and notification.
publish OrderEvent(orderId, state, eventId)
written through a transactional outbox so an order never advances without its event; every event carries an id so consumers dedup and apply it exactly once in effect.

One of these carries the design. The place-order call is the strong path and everything about it exists to guarantee that when the request arrives more than once, the shopper ends up with exactly one order and exactly one charge. It reserves, authorizes and records as one coordinated outcome, keyed by the token minted at checkout and it is the only call worth the cost of a careful distributed transaction. The cart calls are the mirror image: frequent, cached and safe to be a touch stale, because nothing binding is decided until checkout recomputes it. The order-status read is the payoff a shopper watches and the event stream is how the order tells the rest of the company what happened, once and only once.

High-level design

Follow the shopper one hop at a time. A Shopper builds a cart through the Cart service, which persists it to the Cart store and reads it back from cache. When they commit, the Checkout orchestrator takes over. It prices the cart, then drives the place-order transaction. It reserves units through the Inventory service, authorizes money through the Payment gateway and creates the record in the Order service. The order writes its state change and an event together and the Order events log fans that out to Fulfillment + notify and the rest. The diagram colors each box by role: blue for the shopper and the external gateway, violet for the services, amber for the event log, green for the store. The walkthrough reuses the same tags.

Loading diagram...

1) The shopper builds a cart. The Shopper adds, updates and removes items through the Cart service, which persists to the Cart store and serves reads from cache. This path is high-volume and forgiving: a cart badge a second stale is fine, because nothing binding is decided yet. What is punted here is how the cart stays durable and follows the shopper across devices, which the first deep dive owns.

2) Checkout prices the cart. When the shopper commits, the Checkout orchestrator reads the cart, recomputes line totals, tax and promotions against the live catalog and hands back a quote plus an idempotency key. Recomputing rather than trusting the cart is deliberate, because a price or a promo can have moved since the item went in. The cart carried intent; checkout binds the numbers.

3) Place-order runs the transaction. The Checkout orchestrator drives the crux: reserve units through the Inventory service, authorize a hold through the Payment gateway and create the record in the Order service, all anchored to the same key. If any step fails, it unwinds the earlier ones. What is deferred here is how that stays exactly-once under retries and unwinds cleanly on a partial failure, which the second deep dive owns.

4) The order drives the company. The Order service advances the order through its state machine and, on each transition, writes the state and an event together, which the Order events log carries out to Fulfillment + notify and inventory. This is where a placed order becomes shipping, a charge capture and a text message. The trap being deferred is the dual-write between saving the order and publishing its event, which the third deep dive owns.

5) Peak bends the flow, not the money path. Sitting over all of it is admission control. When a doorbuster drops, a waiting room meters the crowd into the buy flow at a rate the backend can serve. The system sheds browse and coarsens stock counts before it ever slows a checkout. Reliability first, cleverness never. The hard part it defers is how the buy flow degrades by priority and survives hot-item contention, which the fourth deep dive owns.

Technology choices

A box with a name on it is not a decision yet. Back each with a real tool, argue why that kind of tool suits this workload and know where it buckles under this load. Slot a mainstream choice into each one and the follow-ups will come from the failure modes.

  • Cart store is a durable wide-column store with a cache in front: Cassandra keyed by customer identity for the durable cart, Redis holding the hot copy so the common read is a millisecond. Right because a cart is small, read constantly, written often and must survive restarts and days of idle and the one access pattern is a point read and point write by customer. The failure story is a cache miss falling back to Cassandra, a little slower but never wrong and the durable copy means a cart is never lost to an eviction.
  • Checkout orchestrator is a saga orchestrator. This stateful coordinator drives reserve, authorize and create-order in order, and runs compensations in reverse on any failure. It is keyed by the idempotency key so a retry resumes rather than restarts. It is right because the alternative, a two-phase commit locking three services together, is slow and brittle at this scale and this failure rate. The failure to plan for is the orchestrator crashing mid-saga, which the durable key and the recorded step state let a retry pick up from instead of running twice.
  • Order events is a transactional outbox on Kafka. An order state change and its event commit together to an outbox table. A relay publishes to Kafka, which fans out to inventory, fulfillment and notification. Kafka is right because it is the retailer event spine and it decouples the order service from every consumer, so a burst queues in the log instead of knocking the order service over. The failure mode to name is a consumer lagging: its effect trails the order state and idempotent dedup by event id makes the eventual catch-up clean.
  • The payment gateway is an external authorize-and-capture service fronted by an idempotency key: authorize a hold at checkout, capture on confirm, void on compensation. It is right because the card networks and PCI vaulting are boundaries you want to keep outside your services and the key lets the gateway collapse duplicate requests into a single charge. The failure to design for is a lost authorization response, where you do not know if the money moved, reconciled against the gateway by the key rather than retried into a possible second charge.
The Walmart angle
Ground the design in the retailer real stack when the interviewer asks and be specific about the event backbone. The order fan-out rides a Kafka event spine that carries an enormous message volume every day at four-nines availability, which is precisely what lets a Black-Friday burst queue instead of toppling the order service. The inventory reservation is the same single-owner-per-item trick from the inventory design: every item routes to one partition and one owner, so a doorbuster on one SKU is serialized rather than raced. This spine hands you decoupling and durability. The saga, the idempotency key, the compensations and the outbox are still yours to design. Naming the event backbone as the buffer and saying plainly that strong consistency belongs on the reserve-and-charge while browse can be eventual, is the reliability-first answer this round rewards.

Potential deep dives

Everything still on the table is won here, in the dives. A retail buy-flow round almost always pushes on the same four corners. How a cart stays durable and follows the shopper. How place-order lands exactly once across three services. How the order drives the company without dropping a message. And what the whole thing does when a doorbuster hits. The idempotent checkout is the crux, so it gets the most room.

1) The cart and where it lives

Start at the cart, because it is the entity most people underrate. A cart looks like a scratchpad, so the instinct is to keep it wherever is easiest and move on. That instinct is the trap. A cart is durable state the shopper owns, it is worth real revenue and it has to follow them from the phone at lunch to the laptop at night. Walk where it can live, because the difference between a cart that vanishes and one that follows the shopper is the whole first impression of the buy flow.

BadThe cart lives on the client or a pinned session

Approach

Keep the cart in the browser, in a cookie or local storage or pin it to one web server memory as a session. Nothing to build server-side, the read is instant because it is right there and for one shopper on one laptop finishing in a single sitting it works fine.

Challenges

It falls apart the moment the shopper moves. Add three items on the phone at lunch, open the laptop at night and the cart is empty, because it never left the phone. A pinned server session dies when that server restarts or the load balancer routes the next request elsewhere, so a routine deploy in the middle of a sale drops carts on the floor. And a cart you cannot see server-side is a cart you cannot recover, re-price or remind anyone about. For a retailer whose shoppers hop between phone, app and desktop across days, a cart that cannot travel is barely a cart.

GoodA server-side session cart in a fast store

Approach

Move the cart to the server, into a fast key-value store keyed by a session id, with a time-to-live. Now any web server can read it, a restart does not lose it and the read stays quick because it is a point lookup in memory. This is the right instinct: the cart is server state, not client state.

Challenges

A session cart still forgets the shopper. Tie it to a session and it evaporates when the session ends or the TTL lapses, so the cart a guest built last week is simply gone. Sign-in is the hard seam. A guest fills a cart, logs in and finds a different cart waiting from last month. Now two carts have to become one without dropping items or silently doubling quantities. And a fast in-memory store alone is not a promise of durability, so a cart worth real money needs to survive more than a cache eviction. Sessions forget. The cart needs an owner that outlives any session.

GreatA persistent cart keyed by the customer, merged on sign-in

Approach

Give the cart a durable home keyed by the customer identity, not a session. A logged-in cart lives in a durable store so it survives restarts, evictions and days of idle, with a hot copy in cache so the common read stays a millisecond. A guest still gets a session cart and on sign-in the two merge: union the line items, sum or cap quantity on a shared SKU, keep the result under one owner. The key move is that the cart holds intent, not a contract. It records which SKUs and how many, never a locked price or a guaranteed unit, because prices move and stock moves. The binding numbers get recomputed at checkout against the live catalog and the live count.

Challenges

You take on merge rules and the truth boundary. Merging asks real product questions: does a shared SKU sum or take the larger quantity, what happens when the merged quantity now exceeds what is in stock, which cart promotion wins. None of that is free and getting it wrong annoys the shopper at the worst moment. Holding the cart as intent instead of a price lock means a shopper can see a price change between adding and buying. Checkout has to surface that honestly rather than swap the number silently. The payoff is a cart that behaves the way a shopper expects. It follows them everywhere. It never quietly vanishes. And it never promises a price or a unit it cannot keep, because the promise is made at checkout, not in the cart.

A cart is durable state the shopper owns, not a session that happens to hold items. Persist it keyed by customer identity in a durable store with a cache in front, merge the guest cart into it on sign-in and treat the contents as intent rather than a contract. Prices, tax and availability are recomputed at checkout against the live catalog and the live count, so the cart never promises a number it cannot keep. That one boundary, the cart holds intent and checkout binds the truth, is what keeps the rest of the buy flow honest.

2) Checkout as a distributed transaction

The dive turns on one hard fact. Placing an order takes three services that all have to agree: inventory has to set the units aside, payment has to secure the money and the order service has to record the sale. Any one of them can fail or time out while the others succeeded. And the shopper, staring at a spinner, will tap the button again. So the hard part is making the whole thing land exactly once when parts of it fail and the caller retries. Retries are the weather here. Walk it slowly, because arriving at the safe design for the right reason is the point of the dive.

BadOne synchronous chain, no idempotency

Approach

Call the three in a row inside the request. Reserve stock, charge the card, write the order, return success. It reads like a plain function, top to bottom and on the happy path it is correct every single time. Sunshine forgives a lot.

Challenges

It breaks the instant a step fails or a caller retries, which under load is constant. Charge the card, then the order write times out and the shopper is out money with no order. Reserve stock, then payment fails and the units are stranded, held for a purchase that never happened and on a doorbuster those phantom holds oversell the item to nobody. Worst of all, a shopper who taps place-order twice, or a client that retries a timed-out call, runs the whole chain twice and gets two charges and two orders. Two charges. One purchase. A multi-step write with no idempotency and no cleanup is a double-charge generator hiding behind a loading spinner.

GoodA saga with compensations

Approach

Treat checkout as a saga: a sequence of steps, each with a compensating action that undoes it. Reserve inventory and if a later step fails, release the reservation. Authorize payment and if a later step fails, void the authorization. An orchestrator drives the steps forward and runs the compensations in reverse on any failure, so a partial failure unwinds instead of leaving money and stock stranded. This is the right shape: no distributed lock, no two-phase commit across three services, just forward steps and known undo steps.

Challenges

Compensations clean up the mess, but they do nothing about the retry. The orchestrator itself can crash between steps and when the caller retries, a saga with no memory starts a fresh run: a second reservation, a second authorization, a second order. You have made partial failure recoverable and left duplicate execution wide open. And duplicate execution is the failure that happens at peak, when everything is slow and everyone is retrying. Undo is solved. Do-it-once is not.

GreatAn idempotency key over an orchestrated saga

Approach

Anchor the whole saga to an idempotency key minted when the shopper starts checkout, one stable token for that attempt. The order service records the intent under that key before any step runs, so the key names a single order-in-progress. Every retry carries the same key. The orchestrator looks it up first. If a run is already underway or done, it returns that order instead of starting a new one. A mashed button and a client retry both land on the one order. Each downstream step is itself idempotent off the same token. One token threads all of it. The reservation is keyed by order id, so a repeat reserve is a no-op. The payment authorization carries the key, so the gateway collapses duplicates into one charge. Authorize a hold at checkout, capture it only when the order is confirmed and release stock and void the hold if the saga has to compensate. One key, one order, one charge, no matter how many times the request arrives.

Challenges

You own the key lifecycle and the in-doubt cases. The key has to be generated early, survive the whole flow and be stored durably with the order intent, because a key you lose is a retry you cannot dedup. Payment adds a hard case: an authorization whose response is lost. You do not know whether the money moved, so you reconcile against the gateway by the key rather than blindly retrying into a possible second charge. Holds need a timer, so an abandoned checkout releases its reservation and voids its authorization instead of stranding both. The reward is the correctness bar this round is graded on: the buy flow reserves once, charges once and yields exactly one order, through crashes, timeouts and a shopper mashing the button.

The Walmart angle
The idempotent place-order is the single decision this round is graded on, because it is the money path a retailer runs for hundreds of millions of shoppers. An interviewer wants to hear two words out loud, idempotency and compensation and to hear you thread one key from the first checkout tap through the reservation, the payment authorization and the order record. Treat the retry as the normal case. At peak the site is slow and everyone is retrying at once. Say plainly that the authorization carries the key, so the gateway dedups it into a single charge. A lost payment response is reconciled instead of retried. Strong consistency belongs on reserve-and-charge while browse stays eventual. That is the reliability-over-cleverness reasoning that separates a retail answer from a generic three-box diagram.

Placing an order is a distributed transaction, and the goal is landing it exactly once under retries. Drive it as an orchestrated saga so a partial failure unwinds by releasing the reservation and voiding the authorization. Then anchor every step to one idempotency key minted at checkout so a retry resumes the same order instead of starting a new one. Authorize a hold, capture on confirm and reconcile an in-doubt payment against the gateway by that key rather than charging again. Reserve once, charge once, one order. That is the bar. Idempotency plus compensation is how you clear it without a distributed lock.

3) The order service and the write path

The saga produced an order. Now that order has to drive the rest of the company: inventory decrements for real, fulfillment picks and packs, notification tells the shopper. The order also has a life. It moves from placed to paid to fulfilled to delivered or branches to cancelled and returned. Two traps hide in that handoff. The order is one record many things want to change and the moment you both save it and tell someone about it, you have a dual-write that can quietly drop the message.

BadA mutable order row with inline downstream calls

Approach

Store the order as a row you update in place and when its state changes, call the downstream services right there: ping inventory, ping fulfillment, ping notification. Simple, direct and you can watch it work end to end in a demo.

Challenges

The inline calls are a dual-write with no safety net. Commit the order as paid, then the call to fulfillment fails and the order says paid while fulfillment never heard, so the package never ships. Or the calls succeed and the commit rolls back and now fulfillment is packing an order that does not exist. A mutable row also erases how it got here, so a wrong state is unrecoverable and an audit of what happened to a charge becomes guesswork. Save-then-notify looks fine until the network drops one message and at Black-Friday volume it drops many.

GoodA state machine that publishes events

Approach

Model the order as an explicit state machine, placed to paid to fulfilled to delivered, with cancelled and returned as branches and forbid the illegal jumps. On each transition, publish an event that downstream services consume: inventory decrements on paid, fulfillment starts on paid, notification fires on each change. Now the order owns its lifecycle and the consumers decouple from it. This is the right structure.

Challenges

Publishing after the commit is still a dual-write. The order commits, then the publish fails and the event is gone with no record it was ever owed. Publish before the commit and a rollback leaves a phantom event for a state that never happened. The state machine fixed the lifecycle and left the same lost-message hole open between the database and the log. You need the save and the publish to be one atomic fact, not two hopeful steps in a row.

GreatA transactional outbox for exactly-once effect

Approach

Write the state change and its outgoing event in the same local transaction, the event into an outbox table beside the order. One commit, both or neither, so an order can never advance without its event and never emit an event for a state it did not reach. A relay tails the outbox and publishes to the log, retrying until the broker acks, so the event is delivered at least once. Consumers make it exactly-once in effect. Each event carries an id, and the downstream consumers dedup on it, so a redelivery decrements once and ships once. The order state machine stays the source of truth, the outbox guarantees the fan-out and the log lets every downstream team consume at its own pace.

Challenges

The outbox adds a relay to run and a delivery lag to accept. The relay is another moving piece to monitor. If it falls behind, downstream effects trail the order state by that lag. That is fine because fulfillment and notification tolerate a short delay while payment did not. Consumers have to be genuinely idempotent, which is real work per consumer, because at-least-once delivery guarantees they will see duplicates. And the outbox table needs pruning so it does not grow without bound. The payoff is a write path with no lost messages and no phantom effects: the order commits once, the world hears about it exactly once and a replay costs nothing.

An order is a state machine and its effects on the rest of the company have to fire exactly once. Advance the order through explicit states. Write each state change and its event in one transaction to an outbox, and let a relay publish to the log so downstream services consume at their own pace. Make every consumer idempotent by event id so a redelivery decrements inventory once and ships once. The order owns its lifecycle, the outbox closes the dual-write hole and exactly-once-effect falls out of idempotency plus dedup, not out of a distributed lock.

4) Black-Friday scale and graceful degradation

The last dive is the day the whole design exists for. A doorbuster drops at a set minute and a national crowd hits place-order in the same second. Two ugly things happen at once. The aggregate load spikes far past a normal day and worse, it concentrates, because the crowd is not spread across the catalog, it is all reaching for the same few items. The buy flow has to stay up, stay correct and decide what to sacrifice first when it cannot serve everyone. Plan the sacrifice in advance, because the peak is not the moment to invent one.

BadAutoscale and hope

Approach

Point the buy flow at an autoscaler and trust it to add capacity when the crowd arrives. No queue, no admission control, every request goes straight at checkout, inventory and payment. On a normal traffic ramp it is genuinely enough. Then the doorbuster hits.

Challenges

A doorbuster is not a ramp, it is a step function and autoscaling lags it by minutes while the spike lands in seconds. Every shopper hits the same hot item, so a single inventory key takes the whole crowd and melts while the rest of the cluster idles. The database saturates, checkouts slow, slow checkouts make shoppers retry, retries add load and the system spirals into congestion collapse right when it is making the most money. That is the spiral. Hoping the cloud scales fast enough is how a sale takes the site down.

GoodRate-limit the front door

Approach

Put a rate limiter in front and cap the requests per second reaching the buy flow, shedding the excess with a try-again. The backend now sees a flow it can survive instead of the full flood and the hot item is no longer hammered without limit. This is the right first move: protect the core by bounding what reaches it.

Challenges

A blunt limiter is unfair and opaque. It sheds randomly, so a shopper who was seconds from paying gets bounced while a fresh arrival gets in and a rejected user just retries, which is more load. It also throttles everything equally, browse and checkout and payment alike, so it can drop an in-flight purchase to make room for someone who is still only browsing. Bounding the load was right. Doing it bluntly, with no fairness and no priority, is what to fix.

GreatA waiting room plus graceful degradation

Approach

Admit the crowd on purpose. A virtual waiting room fronts the buy flow. When demand outruns capacity, a new shopper gets a fair queue position and a real, honest wait, and the system admits them at the rate the backend can safely serve. Inside the gate, protect the money path by priority. An in-flight checkout and its payment outrank a new add-to-cart, which outranks browse. Shed from the bottom. Recommendations and reviews thin out, exact stock counts drop to a coarse in-stock signal, while checkout and payment stay whole. Lean on the log as a shock absorber, so a burst of order events queues instead of pushing back on the order service. And the hot item is handled where it lives. The single-owner-per-item reservation from the inventory design serializes the doorbuster onto one ordered stream, so contention on one SKU cannot oversell it or take the store down.

Challenges

A waiting room is real infrastructure and a product decision, because a queue is a visible wait you choose to show rather than a spinner that pretends. It has to be fair, resistant to refresh-to-skip and honest about position. Fairness is the hard part. Priority shedding means classifying every request by where it sits in the funnel and tuning what degrades before what, which is a business call as much as a technical one. And graceful degradation has to be exercised before the peak, because a degradation path that only ever runs on Black Friday is a path that fails on Black Friday. The reward is a buy flow that bends instead of breaking: it serves the money path first, sheds browsing before buying and turns a stampede into an orderly line.

The retry storm, not the raw request count, is what turns a Black-Friday spike into a double charge or an oversell. When checkout slows, users mash place-order and clients auto-retry, so the same intent arrives two and three times exactly when the system is least able to tell duplicates apart. Without an idempotency key threading the whole flow and dedup on every downstream event, that storm becomes duplicate charges, duplicate orders and stock reserved to nobody. Load you can add servers for. Duplicate side effects you can only design out, with one key per attempt and idempotent steps end to end.
The Walmart angle
Black Friday is the buy flow's whole reason for existing, not a once-a-year load test bolted onto it, and reliability over cleverness is the stance this round is graded on. The Kafka event backbone carries an enormous daily message volume at four-nines availability. A spike has somewhere to queue instead of knocking a system over. And the single-owner reservation keeps a doorbuster on one SKU from becoming an oversell or a melted node. What reads as real here is seeing the peak as a different animal from a busy Tuesday, not the same shape scaled up. The spike in total load and the concentration onto a few hot items are two different problems with two different answers. A waiting room and a log absorb the flood, while a single owner handles the hot item. Naming that split and naming what you degrade before you ever touch checkout, is the reliability-first answer the round rewards.

A flash sale is two problems at once: a spike in total load and a concentration onto a few hot items. Absorb the spike with a waiting room that admits shoppers at a safe rate, plus a log that buffers the burst. Protect correctness by priority, shedding browse and coarsening stock counts long before you ever slow checkout or payment. Handle the hot item where it lives, serialized through a single owner so a doorbuster cannot oversell it. And design the duplicates out with an idempotency key end to end, because you can add servers for load but never for a double charge.

What is expected at each level

This design carries a lot of surface and the rungs separate on how much of it you own before anyone asks. Below senior the buy flow often arrives as a low-level design or a ninety-minute machine-coding round, where the bar is a clean cart model rather than a distributed architecture. At senior and above it becomes a leveling gate. Read the rungs as a climb: a coherent end-to-end flow at mid, the idempotent checkout and the durable cart owned at senior and the outbox and the peak-degradation story carried at staff.

Mid-level engineer

Spend your time on breadth, about eighty to twenty. Breadth over depth here. Reach one coherent end-to-end path: a shopper builds a cart in a cart service, checkout prices it and reserves stock and charges a card and an order gets created and drives fulfillment and notification. Model the cart cleanly, a Cart of CartItems and an Order that is created from it, because in the low-level round that model is most of the grade. Name the double-charge risk at all. When the interviewer asks what happens if the shopper taps place-order twice, reach for an idempotency key rather than shrugging. A working buy flow you can defend, with the interviewer filling in the harder corners, clears this rung.

Senior engineer

Now the balance tips to depth, roughly sixty to forty and this is the level the round is tuned to. Raise the hard facts yourself. Make the cart durable and keyed by customer and explain the guest-to-login merge without being asked. Drive checkout as a saga and put the idempotency key on the board as the thing that makes place-order safe to retry, with compensations that release the reservation and void the authorization on failure. Make the consistency call deliberately: strong on reserve-and-charge, eventual on browse and say why. Arriving at a near-complete buy flow whose double-charge guard, cart durability and saga you flagged on your own is the bar here.

Staff and beyond

At this level depth dominates the whole picture, about forty to sixty. Own the write path end to end. The transactional outbox closes the dual-write hole. Consumers stay idempotent by event id for exactly-once effect, and the order is a state machine other systems only ever react to. Carry the peak story. A waiting room meters the crowd, and priority shedding sacrifices browse before checkout. The log is the buffer that keeps a burst from becoming a backlog, with the hot-item contention handed to the single-owner reservation. Hold the idempotency key, the compensation set and the degradation priorities as deliberate levers and know what bends under pressure, browse to a coarse signal, the money path never. You drive the design and the interviewer course-corrects instead of stepping in to save it. The bar is holding the cart, the checkout transaction, the order write path and the peak as four separate levers while never-double-charge and never-lose-a-cart survive every refinement.

Key Takeaways

  • ✓The buy flow is the money path and it is graded on reliability, not cleverness: never double-charge a card, never lose a cart, place exactly one order per intent and hold up under peak. Spend your effort on those unglamorous guarantees and treat browse and cart reads as the slack you are allowed to keep fast and slightly stale.
  • ✓A cart is durable state the shopper owns. Persist it keyed by customer identity in a durable store with a cache in front, and merge the guest cart on sign-in. Hold its contents as intent instead of a contract, so prices, tax and availability are recomputed at checkout against the live catalog and the live count.
  • ✓Placing an order is a distributed transaction across inventory, payment and orders, and it has to land exactly once. Drive it as an orchestrated saga with compensations that release the reservation and void the authorization on failure. Anchor every step to one idempotency key, so a retry resumes the same order and the gateway collapses duplicates into one charge.
  • ✓The order is a state machine whose effects must fire exactly once, so write each state change and its event in one transaction to an outbox and let idempotent consumers dedup by event id. Under a doorbuster, absorb the spike with a waiting room and the log, serialize the hot item through a single owner and shed browse before ever slowing checkout or payment.
Had your Walmart interview?Tell us what they asked. Reviewed before it goes in, never with your name.
Walmart interview guide

Know what Walmart asks before you walk in.

24 coding · 4 system design · 20 behavioral questions

Get Premium$49 a year · Cancel anytime
Or start freeNo card.

Get their questions first.

InterviewLegend is independent and not affiliated with or endorsed by any company it covers.