Design an E-Commerce System (Amazon / Noon / Daraz)
Difficulty: Tier 3 Asked at: Amazon, Noon, Daraz, most retail companies Time budget: 45–60 min
E-commerce is a system of systems: catalog, search, cart, inventory, orders, payments, fulfillment. The
classic trap is trying to design all of it — you can’t in 45 minutes. The skill is scoping to the hard
core: the inventory / checkout problem, where correctness under concurrency (don’t oversell the last
item) meets scale (flash sales). This study shows how to decompose a huge domain and then go deep where it
matters.
Prerequisites: Microservices, Transactions, Saga Pattern, Distributed Locking
1. Requirements
Functional:
- Browse/search a product catalog; view product pages.
- Cart; checkout; place orders; pay.
- Inventory management — accurate stock; don’t oversell.
- Order tracking/history; fulfillment.
Non-functional:
- Read-heavy browsing (catalog/search) vs transactional, correct checkout.
- Correctness for money & stock — no overselling, no lost/double orders, no double charges.
- High availability; handle flash-sale spikes (huge, bursty).
- Scale to millions of products, millions of orders.
Out of scope (declare!): recommendations (separate), the payment
processor internals (separate), search internals (typeahead).
🚨 Scoping is the first skill this question tests.
2. Decompose the domain
🚨 You cannot design “all of Amazon.” Break it into services and pick the hard one to go deep on:
flowchart LR
Catalog[Catalog Service<br/>read-heavy]
Search[Search Service]
Cart[Cart Service]
Inventory[Inventory Service<br/>⭐ the hard core]
Order[Order Service]
Payment[Payment Service]
Fulfill[Fulfillment Service]
Cart --> Order --> Inventory
Order --> Payment
Order --> Fulfill
Each is independently scalable with its own store (catalog: read replicas + cache; inventory:
strongly-consistent). State the map, then say: “The interesting core is inventory + checkout — let me go
deep there.”
3. The hard core: inventory & checkout without overselling
🚨 The central correctness problem: 500 people try to buy the last 10 units of a flash-sale item at the
same millisecond. You must sell exactly 10, not 11, not 0.
This needs strong consistency on the inventory count — the opposite of the eventually-consistent feeds
elsewhere in this repo. Options:
- Atomic decrement with a conditional update: `UPDATE inventory SET qty = qty - 1 WHERE sku = ? AND qty
0` — the DB serializes it; if 0 rows affected, it’s sold out. Simple and correct for moderate scale.
- Reserve-then-confirm: on checkout, reserve stock (decrement a reserved counter) with a short TTL;
confirm on payment success, or release on timeout/failure. Prevents holding stock forever for abandoned
carts.
- Distributed lock / transaction for multi-item orders (all-or-nothing). (Distributed Locking)
The DB (relational, ACID) is the right tool for inventory — 🚨 money and stock want transactions, not
eventual consistency.
4. Checkout as a distributed workflow (saga)
Placing an order touches inventory, payment, and fulfillment — separate services, so no single ACID
transaction spans them. Use a saga:
1. Reserve inventory → (fail: sold out, stop)
2. Authorize payment → (fail: release inventory, stop)
3. Confirm order →
4. Capture payment → (fail: compensate)
5. Trigger fulfillment
Each step commits locally; failures trigger compensating actions (release reserved stock, void a payment
auth). The order reaches a consistent terminal state (placed or fully rolled back). 🚨 Idempotency
everywhere — retried steps (e.g. a payment call) must not double-charge or double-reserve. (Idempotency)
5. Deep dives
5a. Catalog & product pages (the read-heavy side)
Product pages are read millions of times, change rarely → cache aggressively + CDN for images/static
content; read replicas for the catalog DB. This half looks like every read-heavy design; contrast it
explicitly with the transactional checkout half. Search is a separate inverted-index service.
5b. Cart
Carts are per-user, frequently updated, tolerant of eventual consistency and even loss (annoying, not
catastrophic). Store in a fast KV store (Redis) with persistence; merge guest + logged-in carts. Not the
hard part — say so.
5c. Flash sales / hotspot inventory
A single hot SKU (the sale item) is a hot key with massive write contention on its count. Mitigations:
- Queue/serialize purchases for that SKU (a virtual waiting line).
- Shard the counter (split 100 units into 10 buckets of 10 across shards; decrement a random bucket) to
spread contention — then reconcile.
- Pre-reserve in batches. (Hot Keys, Thundering Herd)
5d. Order consistency & idempotency
An order-placement request may be retried (user double-clicks, network retry) → use an idempotency key
so the same checkout doesn’t create two orders or two charges. Orders are durable, auditable records
(source of truth for payment/fulfillment).
5e. Eventual consistency where it’s safe
Not everything needs strong consistency — order history, recommendations, analytics, “customers also
bought” can be eventually consistent and updated via events (event-driven). 🚨 Reserve strong consistency
for stock and money; use eventual consistency everywhere else — this selective rigor is the senior insight.
6. Bottlenecks & scaling further
- Catalog read load → cache + CDN + read replicas.
- Inventory contention (flash sale) → sharded counters / queued purchases / reservations.
- Cross-service order consistency → saga + compensations + idempotency.
- Order/payment correctness → ACID inventory, idempotency keys, durable orders.
- Spikes → autoscale stateless services, queue buffering, graceful degradation.
7. Trade-off summary
| Concern |
Consistency |
Store |
Why |
| Catalog/browse |
Eventual |
Cache + replicas + CDN |
Read-heavy, rarely changes |
| Cart |
Eventual |
Redis (KV) |
Per-user, loss-tolerant |
| Inventory |
Strong |
ACID relational |
Must never oversell |
| Order/payment |
Strong + idempotent |
ACID + saga |
Money must be correct |
| Recommendations/history |
Eventual |
Event-driven |
Not correctness-critical |
8. Follow-up questions
How do you prevent overselling the last item when 500 people buy it at once?
By making the stock decrement a strongly-consistent atomic operation that the database serializes. The
canonical approach is a conditional update — decrement the quantity only if it's still greater than zero
(`UPDATE ... SET qty = qty - 1 WHERE sku = ? AND qty > 0`) — so the database processes the 500 concurrent
attempts one at a time and exactly the first 10 succeed (rows affected = 1) while the rest see zero rows
affected and are told "sold out." This works because inventory is kept in an ACID store where such
conditional decrements are atomic and isolated, unlike an eventually-consistent store where concurrent reads
could all see "10 left" and all proceed. For higher scale you add reservations (reserve on checkout with a
TTL, confirm on payment) and may shard the counter to spread contention, but the core guarantee comes from
serialized atomic decrements against a consistent store — money and stock are exactly where you *don't* want
eventual consistency.
Checkout spans inventory, payment, and fulfillment. How do you keep it consistent?
With a saga, because those services have separate databases and a single ACID transaction can't span them.
The checkout is modeled as a sequence of local transactions — reserve inventory, authorize payment, confirm
order, capture payment, trigger fulfillment — each committing in its own service and publishing an event.
If any step fails, the saga runs compensating transactions to undo the completed steps: release the reserved
inventory, void the payment authorization, cancel the order. This drives the order to a consistent terminal
state, either fully placed or fully rolled back, without a distributed transaction. Crucially every step is
idempotent and keyed by an idempotency token, so retries (from network failures or double-clicks) don't
double-charge the customer, double-reserve stock, or create duplicate orders — which is essential because
saga steps are retried on transient failure.
How do you avoid designing "all of Amazon" and running out of time?
Scope explicitly and early. State that e-commerce is a system of independent services — catalog, search,
cart, inventory, order, payment, fulfillment — and sketch that decomposition so the interviewer sees you
grasp the whole, then declare which parts are out of scope (recommendations, the payment processor
internals, search internals) and name the hard core you'll go deep on: inventory and checkout, where
correctness under concurrency meets flash-sale scale. This demonstrates the judgment the question is really
testing — recognizing that the interesting, differentiated problem is not the read-heavy catalog (which
looks like every other cached read system) but the transactional, oversell-proof checkout. Spending your 45
minutes deep on that, having framed the rest, beats a shallow tour of every service.
Which parts should be strongly consistent and which can be eventual?
Reserve strong consistency for the two things that must be correct — stock and money — and use eventual
consistency everywhere it's safe, which is most of the system. Inventory counts must be strongly consistent
so you never oversell, and order/payment state must be strongly consistent and idempotent so you never double-
charge or lose an order; these live in ACID stores with transactions and sagas. Everything else tolerates
staleness: the product catalog and browsing (cached, replicated), the cart (loss-tolerant, per-user),
order history, recommendations, "customers also bought," and analytics can all be eventually consistent and
updated asynchronously via events. This selective rigor — being strict exactly where correctness is non-
negotiable and relaxed everywhere else for scale and availability — is the senior insight, because applying
strong consistency everywhere would needlessly cap scale and availability, while applying eventual
consistency to stock or money would be a correctness disaster.
9. What junior / mid / senior answers look like
- Junior: tries to design every feature at once, treats inventory as a normal update, misses overselling
and the flash-sale contention.
- Mid: decomposes into services, uses an ACID store with atomic decrements for inventory, a saga for
checkout, caches the catalog, handles idempotency.
- Senior: scopes ruthlessly to inventory + checkout, designs oversell-proof reservations with sharded
counters/queuing for flash sales, a compensating saga with pervasive idempotency, and — the key framing —
applies strong consistency only to stock and money while everything else is eventually consistent via
events.
Further reading