Skip to content

03. E-commerce Platform — Interview Q&A

~15 min read · Part 4 of 4 (Overview → HLD → LLD → Q&A)

Q1. During a flash sale, 500k requests hit one product with 10k units. How do you prevent overselling? Use a single atomic conditional update — UPDATE inventory SET available = available - qty WHERE id = ? AND available >= qty — and treat "0 rows affected" as the out-of-stock signal. The row lock serializes the 500k requests; the first 10k that satisfy the predicate win, the rest get a clean 409. Add a CHECK (available >= 0) constraint so the database itself can never go negative even if the service has a bug. Common wrong answer to avoid: "Read the stock, check if > 0, then decrement." That read-then-write has a race where two requests both read the last unit and both sell it.

Q2. That hot row can only do a few thousand updates/second. What if the sale needs 50k/minute? Shard the counter: split the 10,000 units into N buckets (say 10 × 1,000), route each request to a bucket by hash, and sum the buckets for display. This multiplies decrement throughput ~N× by turning one hot row into N warm rows. The cost is that the displayed "units left" is an eventually-consistent sum and a request may probe a couple of buckets near sell-out. Common wrong answer to avoid: "Put a distributed lock around the decrement." That serializes everything globally and is slower than the single-row DB lock you already have.

Q3. How do you make sure a customer who double-clicks checkout isn't charged twice? The client sends an idempotency key with the request; the server records completed operations keyed by it. A retry with the same key returns the stored result instead of re-running the charge. Storing the key as a primary key makes "first time vs retry" an atomic insert-or-conflict, with no race. Common wrong answer to avoid: "Disable the button on the frontend." Client-side guards don't survive network retries, refreshes, or two tabs; idempotency must be enforced server-side.

Q4. Checkout touches inventory, payment, and orders — do you use a distributed transaction (2PC)? No. Use a saga: perform the steps in sequence, each idempotent, with compensating actions on failure (release inventory, refund payment). Two-phase commit across a payment processor and your DB is impractical — the PSP isn't a transaction participant — and it holds locks across network calls. The saga trades atomicity for eventual consistency with explicit compensation, which is the right tradeoff here. Common wrong answer to avoid: "Wrap it all in one ACID transaction." You can't enroll an external PSP in your database transaction, and long-held locks across network hops kill throughput.

Q5. Why separate the browse path from the checkout path? They have opposite shapes: browse is read-heavy (30k/s peak), staleness-tolerant, and wants cheap horizontal read scaling; checkout is write-heavy on hot rows and demands strong consistency. Separating services and stores means a flash sale saturating checkout can't slow down browsing, and the browse cache/CDN/replica strategy doesn't compromise inventory correctness. Common wrong answer to avoid: "One service and one database for everything." The read and write requirements conflict; one store forces you to compromise both.

Q6. Where do you accept eventual consistency, and where do you refuse it? Accept it for catalog reads, search results, order-history views, and displayed stock counts — a few seconds' lag there is invisible or harmless. Refuse it for the inventory decrement and payment capture, which must be strongly consistent or you oversell and double-charge. The skill is drawing that line explicitly rather than making everything strong (too slow) or everything eventual (incorrect). Common wrong answer to avoid: "Eventual consistency everywhere for scale." Applied to inventory or payments, it directly causes oversells and lost money.

Q7. A customer reserves stock but never pays. What happens to those units? Each reservation carries an expires_at (say 10 minutes). A sweeper releases expired, unpaid reservations back to available. In our sale, that means abandoned carts don't permanently lock the 10,000 units — they re-enter the pool within minutes for other buyers. Common wrong answer to avoid: "Decrement stock only after payment succeeds." Then two people can both reach payment for the last unit; you must reserve before charging and release on timeout.

Q8. Payment succeeded but the order-write crashed. How do you avoid taking money with no order? The operation is recoverable because payment capture and order creation are both idempotent and keyed. On recovery, a worker sees the idempotency/reservation record with a successful capture and no order, and completes the order (idempotent create) rather than refunding. The invariant: money-in always resolves to an order or an explicit refund, never to nothing. Common wrong answer to avoid: "Log an error and move on." That silently keeps the customer's money with no order — the worst possible outcome.

Q9. How does search stay consistent with the catalog, and what if it's stale? Search is an inverted index updated asynchronously from catalog writes, so it can lag a few seconds. That's fine because search is advisory: it helps users find products, but the authoritative availability check happens at checkout via the conditional update, which returns 409 if the item sold out after the search result was rendered. Common wrong answer to avoid: "Query the source-of-truth DB for every search." Full-text and faceted queries against the transactional store don't scale and couple search load to your order database.

Q10. How do you keep a cart consistent across a user's phone and laptop? Store the cart as server-side state keyed by the user (in Redis with a DB backstop), not in a client cookie. Adding an item on mobile and checking out on desktop then reads the same cart. Cart operations are last-write-wins per line item, which is acceptable for a cart. Common wrong answer to avoid: "Keep the cart in localStorage/cookies." It won't sync across devices and is lost when the user switches context — exactly when abandonment hurts.

Deeper follow-ups

  • How would you support multi-warehouse inventory where the same SKU has stock in several locations?
  • How would you handle price changes between add-to-cart and checkout?
  • How would you design returns/refunds so inventory and ledger stay consistent?
  • What changes for a marketplace with many independent sellers and their own inventory?
  • How would you A/B test ranking on the search/browse path without hurting checkout?
  • How would you throttle a flash sale fairly (virtual waiting room) instead of failing latecomers?

How this round is scored

The e-commerce round tests whether you can hold two contradictory requirements at once — a read-heavy, staleness-tolerant browse path and a write-heavy, correctness-critical checkout path — and design each on its own terms instead of forcing one store to do both. The senior signal is recognizing the inventory decrement as an atomic-conditional-update problem, not a locking problem, and knowing when to escalate to a sharded counter with numbers to justify it. Idempotency for payments, the saga-versus-2PC choice, and the explicit "where I accept eventual consistency" line separate candidates who have shipped commerce systems from those reciting a diagram. Naming the failure that everyone remembers — the oversell and the double-charge — and closing both is what earns the round.