Online store
0-day streakReview

Your progress

Level 1

0 XP100 XP to level 2
0day streak
0best streak
0topics started
0cards reviewed
    Review cards

    Progress is stored only in this browser. No account is required.

    Layer 4 · Full breakdown · 34 min · 14 figures

    Design an online store (Amazon)

    Design a large online marketplace like Amazon or Flipkart: hundreds of millions of products with many sellers each, product pages at very high QPS, stock and delivery promises, an event-driven order pipeline and Prime Day–scale peaks.

    An online store like Amazon or Flipkart looks like one website, but it is hundreds of services pretending to be one. A shopper in Pune opens a phone’s product page and, within a fifth of a second, sees photos, a price from the best of six sellers, “only 3 left”, “get it by Thursday if you order within 2 hrs 10 mins” and star ratings. Each fact lives in a different system and changes at a different speed. Then the shopper taps Buy now, and an order starts a journey through a fulfilment centre, a sort hub and a delivery van. You are designing that store: hundreds of millions of products with many sellers each, product pages served hundreds of thousands of times a second, and an order pipeline that never loses an order — on a normal Tuesday, and on the night Big Billion Days opens and traffic jumps tenfold at midnight.

    Understand the problem

    “Design Amazon” is too big to answer as asked, so scope it early: the shopping experience end to end at the level of services and data flows, going deep only where a store is unusually hard — product pages, the catalog, stock and delivery promises, and orders. Search, checkout and payments are black boxes with clear contracts.

    Functional requirements

    1. Shoppers should be able to browse categories and search for products, with filters such as brand, price, rating and delivery speed, and result pages that show a price and a stock badge for every item.
    2. Shoppers should be able to view a product page with photos and specifications, the variants (size, colour, storage), the featured offer and the other sellers’ offers, whether it is in stock, and a delivery promise for their pincode or zip code.
    3. Shoppers should be able to place an order. Checkout — cart, price recompute, coupons, stock reservation, payment — is a black box that returns a placed order; it is designed in checkout and cart.
    4. Shoppers should be able to track an order from placed to delivered, and cancel or return it.
    5. Sellers should be able to list products and update price and stock, one listing at a time or as bulk feeds of millions of rows.

    Non-functional requirements

    • Fast product pages. Server-side p99 under 200 ms for a product detail page (PDP), p50 under 60 ms. Amazon’s Dynamo paper sets internal SLAs at the 99.9th percentile, because an average hides the customers with the heaviest pages.
    • Reads dwarf writes. About 23,000 PDP views a second on average, 230,000 at a sale peak, plus millions of price lookups a second from result pages; writes are tens of thousands a second.
    • Browse never goes down. 99.99% for browse, product pages and placing orders — under 53 minutes a year (525,600 minutes × 0.0001). Back-office flows such as returns may lag by minutes.
    • Fresh enough on the page, exact at checkout. A price or stock badge may trail the truth by a few seconds (p99 under 5 s). At checkout the price is recomputed and stock is reserved, so a stale page can never sell something that does not exist.
    • Durable orders. Once a shopper sees “Order placed”, the order survives the loss of any machine or zone.
    • Peaks. Prime Day and Big Billion Days bring 5–10× normal traffic, arriving as a cliff at the opening minute rather than a ramp.
    • Scale. 500 million products, 1 billion offers, 2 million sellers, 200 million daily shoppers, 10 million orders a day. For calibration, Flipkart reports 150 million products, 1.4 million sellers and about 4 million shipments on an average day.

    In the interview

    State the consistency contract out loud, early: “The product page is a read model and may be a few seconds stale; checkout is the source of truth for price and stock.” Every caching decision afterwards follows from that sentence, and it heads off the question “why don’t you lock inventory when someone views a page?”

    Out of scope

    • Search ranking and indexing internals. Covered in product search for an online store; here search is a service reading an index that we keep fresh.
    • Cart, promotions, tax and the place-order saga. In checkout and cart.
    • Payment processing — card authorisation, UPI collect, refunds — in the payment system.
    • Limited-stock deal spikes (50,000 units of a ₹9,999 phone and five million people waiting) in flash sale.
    • Recommendation models, ads, warehouse robotics and route planning. We call them; we do not build them.

    The set-up

    Plan the approach

    Draw the store as two halves. The read side — browse, search and product pages — carries about 99% of requests and must stay fast and up even when back ends are not, so it reads only from caches and projections. The write side — seller updates, orders, stock movements — is far smaller but must be correct and durable, so it writes to systems of record and publishes every change as an event. The deep dives connect the two.

    Core entities

    • Product. The canonical item with one ID (Amazon’s ASIN): title, brand, images, attributes, a category and a product type that decides the required attributes. Each size or colour is its own product under a parent.
    • Offer. One seller’s terms for one product: price, condition, who ships it (our fulfilment centre or the seller) and handling time. A popular phone can have a dozen.
    • Listing. The seller’s record: their own SKU, mapped to a product, with the offer and their stated quantity.
    • Inventory record. Units of one offer at one fulfilment centre (FC): on hand, reserved and available.
    • Order. Line items (offer, quantity, price paid), address, payment method — prepaid or cash on delivery (COD) — a state, and one or more shipments, each leaving one FC with its own tracking number.
    • Seller. Ratings, cancellation rate, policies and a settlement account.

    API

    The product page is deliberately two calls: a shell that is the same for everyone and can be cached for hours, and a small, personalised buy box that cannot. The first deep dive explains why.

    GET /v1/products/B0C9ZX5K21
    {
      "product_id": "B0C9ZX5K21",
      "parent_id": "B0C9ZX5P77",
      "title": "Phone Z5 (8 GB RAM, 256 GB, Black)",
      "brand": "Zeta",
      "product_type": "SMARTPHONE",
      "images": ["https://m.example.in/i/9f2c41/1500.jpg"],
      "attributes": { "ram_gb": 8, "storage_gb": 256, "colour": "Black", "battery_mah": 5000 },
      "variations": [{ "product_id": "B0C9ZX5K20", "ram_gb": 8, "storage_gb": 128 }],
      "rating": { "stars": 4.4, "count": 18231 },
      "version": 412
    }
    GET /v1/products/B0C9ZX5K21/buy-box?pincode=411014
    {
      "featured_offer": {
        "offer_id": "of_7Q2",
        "seller": "Seller A",
        "price": { "amount": 24999, "currency": "INR" },
        "fulfilled_by": "MARKETPLACE_FC",
        "availability": "IN_STOCK",
        "promise": { "deliver_by": "2026-10-29", "order_within_s": 7800, "cod": true }
      },
      "other_offers": 5,
      "as_of": "2026-10-27T11:40:02+05:30"
    }

    The rest, sketched:

    GET   /v1/browse?node=1389401031&sort=popular&page_token=…   category page, 24 items
    GET   /v1/search?q=running+shoes&filters=…                   served by the search service
    POST  /v1/checkout/{cart_id}:place                           black box; returns an order_id
    GET   /v1/orders/{order_id}                                  state, shipments, tracking events
    POST  /v1/orders/{order_id}:cancel                           allowed until shipped
    POST  /v1/orders/{order_id}/returns                          start a return
    PUT   /v1/sellers/{seller_id}/listings/{sku}                 match keys (GTIN, brand, model), offer, quantity
    PATCH /v1/sellers/{seller_id}/listings/{sku}                 price or quantity only
    POST  /v1/sellers/{seller_id}/feeds                          bulk file; returns feed_id, processed async

    Back-of-envelope estimates

    Assume 200 million daily active shoppers, each viewing about 10 product pages and 6 category or search pages a day; 10 million orders a day; 500 million products and 1 billion offers. (Method: back-of-envelope estimation.)

    Product pages. 200M × 10 = 2B views a day; 2B ÷ 86,400 ≈ 23,000 PDP views/s on average, about 58,000/s at the evening peak (2.5×). A sale peak at 10× the daily average is 230,000 PDP views/s.

    Result pages. 200M × 6 = 1.2B a day ÷ 86,400 ≈ 14,000/s, so 140,000/s at a sale peak. Each shows 24 products with a price and stock badge: 140,000 × 24 ≈ 3.4M price-and-stock lookups/s — the biggest reader of price data.

    Fan-out. About 10 services per product page: 230,000 × 10 = 2.3M internal calls/s at the peak.

    Media. Roughly 30 images and static files per page view, about 50 KB each: 230,000 × 30 = 6.9M requests/s, and 230,000 × 1.5 MB = 345 GB/s ≈ 2.8 Tbps. Only a CDN carries that.

    Orders. 10M ÷ 86,400 ≈ 116 orders/s on average and about 1,200/s at a sale peak. Each order produces about 12 state and tracking events in its life: 120M a day ≈ 1,400 events/s, about 14,000/s at the peak.

    Seller writes. If half of the 1B offers change price or stock once a day — repricing bots change some far more often — that is 500M ÷ 86,400 ≈ 5,800 updates/s, about 30,000/s when repricers rush before a sale. Against 3.6M price reads a second (3.4M from result pages plus 230,000 buy boxes), reads outnumber writes by about 120 to 1.

    Storage.

    • Product content: 500M × 20 KB = 10 TB of attributes and descriptions in several languages, without images.
    • Images: 500M × 8 images × 300 KB across all sizes = 1.2 PB in object storage behind the CDN.
    • Offers: 1B × 500 B = 500 GB.
    • Stock view: 1B offers × 64 B = 64 GB per region — it fits in memory on a handful of nodes.
    • Delivery lanes: 19,000 pincodes × 100 FCs = 1.9M lanes × 200 B ≈ 380 MB — it fits in every promise-service instance.
    • Orders: 10M a day × 3 KB = 30 GB a day ≈ 11 TB a year.

    Tip

    Storage is not the hard part: apart from images, everything fits on modest clusters. What shapes the design is fan-out (230,000 pages a second, each touching about 10 services) and freshness (3.6 million price reads a second that must reflect a seller’s change within seconds).

    High-level design

    Build the store one requirement at a time. By the end of this section it works end to end, in one region, at moderate traffic; the deep dives make it fast, correct and survivable.

    Start with the bytes that never change. Images, JavaScript and CSS carry a content hash in their URL, so a CDN can cache them for a year. Everything else goes through an API gateway with a backend-for-frontend (BFF), which authenticates signed-in shoppers (browsing works anonymously), works out the pincode from a saved address or cookie, and routes the call.

    A browse service owns the category tree (Amazon calls each node a browse node). A category page is a search query with a category filter, so the search service answers it from an index with facets for brand, price and rating. Building and ranking that index is its own problem; here it matters only that catalog events feed it.

    Step 1: media comes from the CDN; category and search pages go through the gateway to the browse and search services.Notice The 2.8 Tbps of images never touches the gateway — only about 1% of media requests, the cache misses, reach the origin.

    Every result card shows a price and a stock badge — the 3.4M lookups a second. For now the index carries a copy of each; keeping it fresh is deep dive 5.

    Shoppers should be able to view a product page

    A product page is a composition, not a record. A page composer inside the BFF takes the product ID and pincode and calls, in parallel, the services that own each part: catalog (title, images, specifications, variants), offers and pricing (every seller’s offer and the featured one), availability (in stock, only N left, out of stock), delivery promise (by when, and whether COD is allowed), reviews and recommendations. Each service owns its store; nobody else reads it directly.

    The Dynamo paper describes Amazon’s pages the same way: one page request calls over 150 services, through stateless aggregator services that cache heavily. Our composer is one such aggregator, and it splits calls into two classes: critical answers (catalog, offer, availability, promise) without which you cannot sell, and optional ones (reviews, recommendations, ads) that can be left out.

    Step 2: the page composer fans out to six services in parallel; each owns its own store.Notice The split: four answers are needed to sell the product, two only make the page nicer. That split decides what happens when something is slow.

    Because the calls run in parallel, the page takes as long as the slowest call, not the sum — which turns ugly at scale, as deep dive 1 shows.

    Shoppers should be able to place an order

    The shopper taps Place order and the checkout black box does the synchronous work: it re-prices the cart, applies coupons and tax, reserves stock at a fulfilment centre, and takes payment or marks the order cash on delivery. It then calls the order service with an idempotency key, so a retry never creates two orders.

    The order service writes the order to the orders DB and, in the same transaction, a row in an outbox table; a relay publishes that row to an order-events topic in Kafka. Every later step — choosing the FC, shipping, notifying — consumes those events instead of running inside the shopper’s request (deep dive 4).

    Shoppers should be able to track an order

    A fulfilment service consumes “order confirmed”, decides which FC ships which items (splitting the order into shipments if needed) and sends each shipment to that FC’s warehouse management system (WMS) for picking and packing. At handover a carrier starts scanning: mother hub, delivery hub, out for delivery, delivered or failed. A tracking service takes the scans by webhook or polling, removes duplicates, maps carrier codes to ours and publishes shipment events, which move the order’s state and trigger notifications.

    Steps 3 and 4: checkout hands a placed order to the order service; fulfilment and tracking then run on events.Notice The shopper’s request ends at “placed”. Fulfilment centres, carriers and scans all happen later, connected only by the event log.

    “My orders” reads the orders DB, sharded by customer ID so one shopper’s orders sit together. Cash on delivery is a wrinkle worth raising unprompted: a COD order is confirmed by a risk check, not a payment, and its money arrives only at the door — or never, if the parcel is refused.

    Sellers should be able to list products and update price and stock

    A seller sends a listing — their SKU, match keys such as the GTIN barcode, an offer and a quantity — through the portal, the API or a bulk feed. The listing service validates it against the product type schema (a phone needs RAM and storage; a saree needs fabric and length), matches it to an existing product or creates one, and writes content to the catalog DB and price and quantity to the offer DB.

    Then every reader must hear about it: page caches, the search index, the offer and stock read models, the featured-offer calculation. Instead of the listing service calling each, change data capture (CDC) reads each database’s commit log and publishes every committed change to a catalog-changes topic that readers consume.

    Step 5: sellers write to the systems of record once; change data capture fans every committed change out to the readers.Notice The listing service knows nothing about caches or the search index. Adding a new reader means adding a consumer, not changing the writer.

    That is a working store, fine for a modest launch. At 230,000 product pages a second, a billion offers and a midnight sale, almost every box above breaks in an interesting way.

    Deep dives

    How do we serve product pages at 230,000 requests a second?

    At a sale peak the composer makes 2.3 million internal calls a second, and each page is only as fast as the slowest of its calls.

    Bad: one database behind the page, or calls in a row. Rendering from the catalog database means about 2.3 million queries a second on a system of record that sellers also write to. Sequential calls are no better: ten calls at a 15 ms median is 150 ms at the median.

    Good: parallel fan-out with a cache in each service. Now the page takes as long as its slowest call — and the arithmetic is unkind. If each call is slower than its own p99 on 1% of requests, the chance a page waits on at least one slow call is 1 − 0.99N: 18% of pages at 20 calls (ten services plus their own calls), 78% at the 150-plus services the Dynamo paper describes. Google’s “The Tail at Scale” paper measured the same effect: a leaf p99 of 10 ms became 140 ms once a request waited for all its leaves.

    Fan-out turns rare slow calls into common slow pages: the share of pages that wait on at least one call slower than its p99.Notice At 20 parallel calls almost one page in five is slow. To hold the page’s p99, each call must hold the budget at about its 99.95th percentile.

    So a dependency’s p99 is not good enough for a page’s p99: with 20 independent calls, each must meet the deadline on 1 − 0.01 ÷ 20 = 99.95% of requests. That is why Amazon sets SLAs at the 99.9th percentile, and why per-service caches are only half an answer — they lift the median, not the tail.

    Great: a cacheable shell, deadlines, hedging and a strict critical/optional split. Four techniques together:

    1. Split the page by how it changes. The shell — title, images, specifications, variants — is the same for everyone and changes a few times a month: cache it as a rendered fragment in the BFF and at the CDN, with a long TTL and a purge on change (deep dive 5). The buy box — offer, price, stock, promise — depends on pincode and membership and changes by the second, so it is a separate, small call. At a 97% shell hit rate the catalog store sees 230,000 × 3% ≈ 6,900 reads a second.
    2. Read models, not computation. Offers, stock and promises come from in-memory projections kept current by events (deep dives 3 and 5), so a buy box is a few key lookups, batched as one multi-get per service — vital for result pages with 24 products.
    3. Deadlines and hedged requests. Each call carries a deadline, so a callee drops work nobody will use. For critical, idempotent reads the composer sends a hedged request to a second replica if the first has not answered by the call’s p95; The Tail at Scale reports hedging after 10 ms cut a p99.9 from 1,800 ms to 74 ms for 2% more requests. Never hedge writes.
    4. Degrade on purpose. Optional calls are cut off at 100 ms and the app lazy-loads them later; critical calls get until 150 ms. The price and buy button are never dropped — and never invented. If the offer service is down, the composer serves the last known offer under a soft TTL of seconds and a hard TTL of minutes (a pattern from Amazon’s caching guidance); past the hard TTL it shows “See all buying options”. Checkout re-prices, so seconds of staleness are safe; a made-up number is not.
    One product page request against its 200 ms budget: critical calls in parallel, a hedge for a slow replica, and an optional call dropped at its cut-off.Notice The page waits for optional calls only until their cut-off: the hedge rescues the critical promise call, and the late recommendations row is dropped instead of setting the page time.

    One trap caught Amazon itself. Around 2001 the site showed shipping speeds for every product on a page — 25 or more — read from a supply-chain database through a cache on each web server, with a fallback to the database if the cache was unhealthy. Months later the caches failed together, every web server hit the database, it locked up and took the website down — and the fulfilment centres that shared it stopped too.

    Never fall back from the cache to the source

    A cache that absorbs 97% of reads is load-bearing: the database behind it is sized for 3%. When the cache fails, serve stale data from a local copy, shed the excess or show less — but cap the rate that reaches the source. Test with the cache off before the sale, not during it.

    Hot products need one more guard: when a deal’s shell is purged, 20,000 requests a second miss at once. Request coalescing sends one load per key per host while the rest wait for it — at most 900 loads across 900 composer hosts, not 20,000 (see hot keys and cache stampedes).

    Where the read requests land at a sale peak of 230,000 product pages a second (log scale).Notice How steep the funnel is: about 10.8 million read requests a second in total, but only around 7,000 reach a system of record.

    You gain a page whose p99 is set by in-memory lookups and a few hedged calls, and a back end that sees about 0.06% of read traffic (6,900 of roughly 10.8 million requests a second). You pay with three copies of every fact — truth, projection, cache — that must be kept in step (deep dive 5), and a page that may be a few seconds old.

    How do we model a catalog where a phone, a saree and a spark plug all fit?

    A marketplace catalog has two problems a normal product table does not. Products differ wildly — a phone has RAM, a saree has fabric and length, a spark plug has a thread size and the cars it fits. And one physical product is sold by many sellers who each describe it differently.

    Amazon’s seller API documentation defines the nouns precisely. A product (an ASIN) is the canonical item, its facts reconciled from the listings of every seller of it. Each colour or size is its own product, grouped under a parent into a variation family with one page. A listing is a seller’s record, keyed by their SKU; an offer is their price and terms. The featured offer — popularly the Buy Box — is the one whose Add to Cart button sits on the page.

    One product page, four kinds of record: a product-type schema, a variation family, a seller listing matched by barcode, and competing offers.Notice The cheapest offer did not win. The featured offer is chosen on landed price, speed and seller quality, and it is computed when offers change, not when the page is viewed.

    Storing attributes. The tempting relational designs both fail at this variety.

    Wide relational table Entity–attribute–value (EAV) Document per product, checked by a product-type schema
    Shape One column per attribute One row per product, attribute and value One JSON document per product ID
    New category Migrate a 500M-row table No schema change Publish a new schema version
    Read one product One row, mostly nulls About 60 rows, reassembled One key lookup
    Filter “8 GB RAM and 256 GB” Easy if indexed One self-join per attribute Done in the search index instead
    Validation Column types only Almost none Required fields, enums and units per type
    You pay Thousands of sparse columns; slow migrations Slow reads; no type safety Validation and filtering live outside the database
    Three ways to store product attributes

    Bad: a wide table. Thousands of mostly null columns, and a migration on a 500-million-row table for every new category.

    Bad: entity–attribute–value. Flexible, but a product page becomes a 60-row fetch, every filter a self-join, and “8”, “8 GB” and “8GB” all land in the RAM field.

    Great: documents plus a schema registry, split by change rate. Store each product as a JSON document keyed by product ID in a sharded document or wide-column store — 10 TB is 64 shards of about 160 GB. A versioned product-type schema owned by category teams sets required attributes, types, allowed values and units, and the listing service rejects documents that fail it. Filtering belongs to the search index, which holds a denormalised copy. Offers live in a separate table keyed by product ID with offer ID as sort key, so “all offers for P-80” is one partition read; they change far more often than content, and mixing the two would make every price change rewrite a 20 KB document and purge a page shell.

    Matching listings to products. If every seller created their own product, a popular phone would appear twelve times with offers and reviews split twelve ways. So the listing service matches before it creates, in order of confidence:

    1. Exact identifiers — GTIN, EAN or UPC barcodes, ISBNs. Most branded goods match here.
    2. Brand plus normalised model number — “SM-S921B” and “sm s921b” are the same phone.
    3. Fuzzy match on title, key attributes and image similarity, producing a score.
    4. Human review for scores in the uncertain middle; below it, a new product is created.

    When sellers disagree, the catalog reconciles per attribute: the brand owner beats a reseller, a seller with an accurate history beats a new one, and conflicts on safety fields (voltage, allergens) go to review.

    Choosing the featured offer. Score each eligible offer on landed price, delivery speed to the region, seller quality (rating, cancellations, late shipments) and stock. Do it when an input changes and store the winner on the offer summary per region, since speed depends on location. Per page view that would be 230,000 scoring runs a second; on change it is at most about 30,000, and the page just reads the answer.

    How do we show stock and delivery promises without hammering inventory?

    Stock truth lives in the inventory service: per offer per FC, units on hand and units reserved, so available = on hand − reserved. It changes on receipts, reservations, picks and restocked returns. Checkout’s reservation is a conditional decrement here — the only place overselling is prevented (see checkout and cart and contention and double booking). The question is how 3.6 million badge lookups a second learn about it.

    Bad: pages ask the inventory service. That is 3.6 million calls a second against a system whose job is serving checkouts and warehouses — exactly the shape of Amazon’s 2001 outage from deep dive 1.

    Good: a short-TTL cache in front of inventory. It cuts load most of the time, but it is still pull: a cold cache or a cache outage sends the full read load to inventory, and the TTL trades staleness against load.

    Great: push a projection, and make staleness an explicit contract. Inventory publishes stock-events whenever an offer’s available count changes at an FC. A projector (a stream processor) folds them into a tiny record per offer per delivery region — the badge and the FCs that can ship there — in an in-memory stock view replicated to every region. Pages read only that; nothing on the read path can reach inventory.

    Pulling stock from the source of truth on every page view (left) versus pushing a projection that pages read (right).Notice What a failure does in each design: on the left a cache outage takes down the website and the warehouses; on the right even an inventory outage leaves pages showing the last known stock.

    Three details make the projection cheap and safe:

    • Bucket, then publish only on change. A page needs “in stock”, “only 3 left” (exact below 10) or “out of stock” — not 4,812. Writing only when a bucket changes means most of the 15 million units sold a day (10M orders × 1.5 items) write nothing: 4,812 to 4,811 is still “in stock”.
    • Staleness is a contract. The badge may lag by seconds (p99 target 5 s). If two shoppers race for the last unit, both see “only 1 left”; the reservation lets one win and the other sees “just sold out — 2 other sellers have it”. A displayed count is a hint; a reservation is a fact.
    • Static stability. An inventory outage freezes badges at their last values instead of breaking pages — Amazon’s advice is to push data proactively so there is nothing to fall back from. If a fast-selling offer’s record goes unrefreshed for, say, 10 minutes, downgrade “in stock” to “limited stock”.

    Delivery promises. “Get it by Thursday if you order within 2 hrs 10 mins” is the most valuable line on the page. Flipkart has described its promise engine, and the mechanics are general. The date is the sum of processing time at each node — source FC, a mother hub that sorts for a region, a delivery hub for the last mile — and transit time between them, constrained by capacity. An FC works in waves, each with a cut-off and a capacity; an order needs its pick-to-dispatch time (Flipkart cites 30–180 minutes) before the cut-off. Miss it, or find the wave full, and the order spills into the next wave and often loses a day to a missed truck. Flipkart runs only 6–8 cut-offs a day per FC, across about 19,000 pincodes.

    How ten minutes past a wave’s latest start costs a day: two orders for the same phone, from a Mumbai-area FC to Pune.Notice The countdown on the page (“order within 20 min”) is simply the time left before wave 1’s latest start at 12:00; the date follows from the truck that wave feeds.

    So the promise service holds four inputs in memory: the FCs with stock for this region (from the stock view); the lane table of transit days from each FC to each pincode (380 MB); each FC’s wave schedule and remaining capacity, pushed every few seconds; and serviceability per pincode, including whether COD is allowed. Per request it walks at most five candidate FCs — next feasible wave, the truck it feeds, transit — and the earliest date wins; the countdown is the time to that wave’s latest start. That is microseconds per page, about 1.15 million FC evaluations a second at the peak.

    Capacity keeps the promise honest: when a wave fills, new promises move to the next one, so the page says Friday rather than breaking a Thursday promise. During Big Billion Days, Flipkart reserves part of each wave for priority cohorts so a flood of orders cannot spill everyone. Like stock, the page’s promise is a projection; the authoritative step is fulfilment assigning the order to a wave.

    How do we process an order reliably after checkout?

    Checkout hands over a placed order: line items with prices paid, stock reserved at an FC, and a payment that is authorised or marked COD. From here the order lives for days, crosses half a dozen teams’ systems and must never be lost, stuck or done twice. Model it as an explicit state machine.

    The order state machine: the happy path along the top and down the right, cancellations and returns branching off it.Notice Cancellation is only possible before “Shipped”. After that, an unwanted parcel becomes a return-to-origin or a return — physical processes, not database updates.

    Bad: do everything inside the checkout request. Book the FC, create the shipment, send the SMS and email — calls in a row while the shopper waits. If the fourth fails, what do you undo? If the client retries, does the SMS go twice?

    Bad: write the order, then publish an event. Committing to the database and then publishing to Kafka is a dual write with no transaction around it. Crash in between and the order is never fulfilled; publish first and roll back, and the warehouse ships a phantom. A 0.01% failure rate at 1,200 orders a second is 1,200 × 0.0001 × 3,600 ≈ 430 stuck orders an hour.

    Good: a workflow engine runs each order. Retries, timers and a visible history come free. The cost: with a 30-day return window, 10M orders a day means about 300 million open workflows, and the engine sits under every step of every order (see workflow engines).

    Great: the state lives in the orders DB, changes leave through an outbox, and every consumer is idempotent.

    1. One transaction, two rows. The new state and an event row in an outbox table commit together or not at all. A relay — CDC on the outbox table — publishes to order-events, keyed by order ID so one order’s events stay in order. This is the transactional outbox.
    2. Conditional transitions. Every state change is guarded by the expected current state, so a duplicate “packed” or a late “allocated” after “shipped” updates zero rows and is skipped. The state machine is the idempotency check.
    3. Idempotent side effects. Consumers that act outside the database — an SMS, a pickup booking — record the event ID in an inbox table with their work, so redeliveries are recognised: at-least-once delivery, effectively-once effects.
    4. Timers where they belong. Some transitions are driven by the clock: an unapproved UPI collect cancels after 15 minutes; a seller who misses the handling time has the order cancelled and refunded; a return window closes. Store an indexed next_deadline and let a scheduler pick up due orders — or use a workflow engine for just these branches.
    BEGIN;
    UPDATE orders
       SET state = 'CONFIRMED', version = version + 1
     WHERE order_id = 'ord_81Kq' AND state = 'PLACED';          -- guard: expected state
    
    INSERT INTO outbox (event_id, order_id, type, payload)
    VALUES ('ev_19af', 'ord_81Kq', 'OrderConfirmed', '{"fc_hint":"BOM4","cod":true}');
    COMMIT;
    -- If the UPDATE touched 0 rows, roll back: this event was a duplicate or arrived late.

    Allocation. Fulfilment usually ships from the FC where checkout reserved stock, but re-plans when a unit is damaged, a wave is full and another FC can still make the date, or splitting beats waiting. Each decision is an event (“allocated”, “allocation changed”, “split into 2 shipments”), so tracking follows automatically.

    Cash on delivery. A COD order is confirmed by a risk check — value caps, pincode eligibility, past refusals — sometimes with a confirmation call. “Delivered” is also when payment is captured, and the courier’s cash is reconciled days later. A refused parcel returns as a return to origin (RTO), costing two trips and a restock, which is why COD eligibility is scored, not a checkbox.

    Sizing. At the peak, 1,200 orders a second write about 1,200 order, 1,800 item and 1,200 outbox rows, plus about 14,400 transitions and their outbox rows — roughly 33,000 row writes a second. Across 16 primaries sharded by customer ID that is about 2,000 each, comfortable for PostgreSQL or MySQL with synchronous replication to a second zone. For 14,400 events a second, 48 partitions with consumers handling 1,000 a second each leave more than 3× headroom.

    How do we keep prices and catalog changes flowing to every cache and index?

    A seller in Surat drops a kurta from ₹1,299 to ₹999. Within seconds that must reach the offer read model, the featured-offer calculation, page shells and CDN copies, the search index that sorts by price, and the price tracker. At a sale peak, repricers send about 30,000 such changes a second.

    Bad: the writer updates everyone. Commit, then update the cache, then the index, then the rest. A failure halfway leaves them out of step for good, retries reorder updates, and every new reader changes the writer.

    Bad: rely on TTLs. Freshness then equals the TTL: five minutes means five-minute-old prices; five seconds means the long tail of 500 million products almost always misses.

    Good: change data capture into Kafka. A CDC connector tails each database’s commit log (write-ahead log or binlog) and publishes every committed change to catalog-changes. The writer only writes its database; consumers subscribe independently and can replay history to rebuild themselves. Nothing is lost, because the log is the commit (see change data capture).

    Great: ordered per product, guarded by versions, measured against an SLO. CDC alone still leaves three gaps.

    • Order per key. Partition by product ID, so one product’s changes are consumed in commit order while different products proceed in parallel (see message ordering per key).
    • Versions beat ordering bugs. During a rebalance, an old consumer frozen in a garbage-collection pause can wake and write an update the new owner already superseded. Carry a version with every change — the row version or the commit’s log position — and apply a write only if it is newer than the stored one.
    • Separate lanes. Price and stock get their own topic, so a two-million-row description feed cannot delay a price change.
    A zombie consumer writes a stale price after a rebalance: a blind write lets it win (left); a versioned write ignores it (right).Notice Both consumers acted correctly on what they had. The fix is not better timing but making the cache refuse anything older than what it already holds.

    For rendered shells and CDN copies, purge rather than set: delete the key and let the next request rebuild it from the read models, so a late purge is harmless — with request coalescing, or a deal’s price change becomes a stampede.

    The freshness budget. Set an SLO — a price change on the product page within 5 s at p99, in search within 60 s — and spend it: a few hundred milliseconds for CDC capture, tens for Kafka and the projector, the rest for CDN purges. Measure it end to end with a canary listing whose price a robot changes every minute while probes in each region time its appearance, and alarm on consumer lag above 2 seconds.

    Sizing. 30,000 changes a second over 64 partitions is about 470 per partition; a projector applying 2,000 conditional writes a second per partition has 4× headroom for catch-up. Seven days of history to rebuild any read model costs 500M × 1 KB × 7 ≈ 3.5 TB, about 10.5 TB with three replicas.

    How do we survive Prime Day and Big Billion Days?

    Sale events are scheduled overloads. During the four days of Prime Day 2025, AWS reported DynamoDB (serving the Amazon.com sites, Alexa and every Amazon fulfilment centre) peaking at 151 million requests a second, and ElastiCache at over 1.4 trillion requests in one minute — about 23 billion a second. Different workloads, so the ratio is only indicative, but it is the shape of this design: roughly 150 cache requests per database request. Big Billion Days adds a cliff: the sale opens at midnight and traffic steps rather than ramps.

    An illustrative sale opening at midnight: offered load against pre-scaled capacity and against capacity that only scales when it sees the load.Notice The shaded window: capacity that waits to see the load spends the first 23 minutes of the sale overloaded. Pre-scaled capacity also survives losing an Availability Zone at the peak.

    Bad: trust autoscaling. It needs minutes of high load to trigger and more to boot instances, which then start with cold caches. Databases, cache clusters and Kafka do not scale in seconds at all. A cliff at 00:00:00 is over before the scaler decides.

    Good: forecast, pre-scale, test, freeze. Take last year’s peak, apply growth and the marketing plan, provision days ahead, and prove it:

    • Size every tier for the peak plus a lost zone. The composers need 230,000 ÷ 400 pages per instance = 575 instances. Amazon’s static-stability practice runs each of three zones at about 66% of its load-tested level: 575 ÷ 0.66 ≈ 870, so plan about 900. Repeat for read-model nodes, Kafka brokers and databases.
    • Load-test to breaking, not to target. Replay recorded traffic past the forecast until something breaks. Amazon ran more than 6,800 fault-injection experiments for Prime Day 2025.
    • Warm caches and freeze changes. Replay the top 10 million product IDs through the shell cache before opening, and allow no deploys or migrations that week except fixes.

    Great: also decide in advance what to sacrifice, and keep failures small.

    Shed by priority, at the front door. Amazon’s load-shedding guidance says to protect goodput (requests answered fast enough to be useful), to shed as early as possible rather than let every service invent priorities, and that a crawler is worth less than a human. So tag each request with a priority at the edge and shed from the bottom:

    Priority Traffic Under overload
    P0 Place order, payment callbacks, order events Never shed; reserved capacity
    P1 Buy box, add to cart, stock and promise reads Shed last; serve stale before failing
    P2 Product shells, category and search pages Serve from cache; simpler result pages
    P3 Reviews, recommendations, personalised rows Switch off with a brownout flag
    P4 Crawlers, analytics beacons, seller reports, bulk feeds Throttle hard or defer until after the peak

    Cells. Run the read side as identical, independent cells — each a full copy of the stateless tiers, caches and read-model replicas — and route shoppers by a hash of their ID. Cap a cell at a load-tested size, say 60,000 pages a second, so 230,000 needs four cells plus a spare. A bad deploy or hot-key meltdown then hurts a quarter of shoppers, not all (see cell-based architecture).

    Static stability. The read side depends only on data already pushed to its region, so if deploys, configuration, the seller portal or even inventory has a bad hour, pages keep serving the last known state, and losing a zone needs no scaling action.

    Isolate the spikiest deals. A ₹9,999 phone with 50,000 units and five million people waiting is a flash sale, with a waiting room and atomic counters; route those deal IDs there.

    Regions. Run the read side active-active in two or three regions, each with projections fed from Kafka. Each shopper’s orders are homed in one region, replicated synchronously across zones and asynchronously to another region (see multi-region and active-active).

    In the interview

    Name what you would turn off before you are asked: “At 10× load, recommendations and reviews go first, crawlers are throttled, search pages simplify, and the buy box serves slightly stale stock. Checkout and order capture keep reserved capacity, and nothing ever shows a price we can’t honour.”

    The final design

    The top band is the read side, answering from memory in every region; the bottom bands are the writers; Kafka in the middle is the only way facts move between them.

    The complete store: a read side served from projections, a write side for orders and sellers, and one event log connecting them.Notice Every arrow into the read models comes from Kafka. No page view can reach a system of record, and no writer has to know who reads its data.

    Follow the main paths through it:

    1. Media and shells. Images, scripts, styles and (for anonymous visitors) product shells come from the CDN; about 99% never reach an origin.
    2. Page request. The request, with the pincode, reaches the page composer, which sets a 150 ms deadline for critical calls and 100 ms for optional ones.
    3. Read models answer, in parallel and batched: the shell from the fragment cache, the offer summary with its precomputed featured offer, the stock badge, the promise from in-memory lanes and wave capacity, and optional reviews and recommendations. Slow replicas are hedged; slow optional calls are dropped.
    4. Order. Checkout re-prices, reserves stock and takes payment or runs the COD risk check, then calls the order service with an idempotency key; the order and its outbox event commit together.
    5. Fulfilment. The outbox relay publishes to Kafka; fulfilment allocates FCs and waves, the WMS picks and packs, and carrier scans flow back as events that move the order’s state and trigger notifications.
    6. Sellers and stock. The listing service validates, matches and writes the systems of record; FCs report receipts, picks and returns to inventory.
    7. Propagation. CDC publishes every committed change to Kafka keyed by product; projectors apply versioned updates to read models, purge shells, update the search index and re-pick featured offers, within 5 s at p99 for prices.

    Check it against the requirements. In-memory reads, hedging and a 150 ms critical deadline keep the PDP p99 under 200 ms. The read side depends only on data pushed to its region, runs in cells and survives a lost zone at the peak. Prices and stock reach pages within seconds and are authoritative at checkout. Orders replicate synchronously across zones before “placed”, and the outbox guarantees their events. Capacity is pre-scaled to 15×, still 10× after losing a zone, with a written list of what to switch off first.

    What interviewers look for by level

    Area Mid-level Senior Staff
    Scope Lists features Splits read and write sides; states “stale page, authoritative checkout” Scopes search, checkout and payments out explicitly; sets SLOs per page section
    Product page A cache in front of the database Parallel fan-out, per-service caches, CDN for media Tail-latency maths, deadlines, hedging, critical versus optional, no fallback to the source
    Catalog One products table with columns Product versus offer; document store Variation families, product-type schemas, matching and reconciliation, featured offer on change
    Stock and promise Queries inventory on every view TTL cache; reservation at checkout Pushed, bucketed projection; static stability; wave- and capacity-aware promises
    Orders Synchronous calls in checkout Events through Kafka, idempotent consumers Outbox, conditional transitions, timers, COD and RTO, sizing
    Change propagation Waits for TTLs CDC into Kafka Per-product ordering, versions, purge versus set, a freshness SLO measured with canaries
    Peaks “Autoscaling handles it” Pre-scale, load-test, warm caches Priority shedding, cells, zone-loss headroom maths, isolating flash deals, regional homing

    Follow-up questions

    What if the price on the page differs from the price at checkout?

    Checkout always re-prices. If the price fell, charge the lower one; if it rose, show “Price changed from ₹999 to ₹1,049” and ask the shopper to confirm — never charge a price they did not see. Count mismatches: a sudden rise means CDC lag or a stuck projector. The opposite failure is a seller typing ₹99 for a ₹9,900 television; hold price drops over, say, 50% for confirmation, and cap quantities at a newly lowered price.

    What if one product goes viral?

    A celebrity mentions a pressure cooker and its page gets 50,000 views a second. The shell is one CDN object; the hot spot is the buy box, one key on one read-model shard. A tiny in-process cache with a 1-second TTL means 900 composers make at most about 900 reads a second instead of 50,000. If the item is also scarce, move it to the flash-sale path.

    What if a seller uploads two million listings at once?

    Feeds land in object storage and are processed asynchronously: a pipeline validates and matches rows in batches under a per-seller quota, and CDC emits the results on the low-priority content topic, so nobody’s price changes wait. At 2,000 rows a second, two million rows take 1,000 seconds, about 17 minutes, with progress and a per-row error report.

    What if a whole region fails during the sale?

    Reads shift by DNS or anycast to the surviving regions — affordable only if, with three regions, each can carry 1.5× its share, so it normally runs at about 66%. Shoppers homed there are re-homed and the orders replica is promoted, losing at most a few seconds of writes, reconciled from idempotency keys and the payment provider’s records. FCs keep working locally; promises turn conservative until confirmed (see disaster recovery).

    What if fulfilment centres fall behind and promises start slipping?

    Wave capacity already feeds the promise service, so full waves push new promises out. Beyond that, watch the promise kept rate per FC and pincode, re-allocate open orders to FCs that can still make their dates, reserve capacity for priority cohorts, and warn shoppers early. A promise missed quietly costs more trust than a later one made honestly.

    How to say it in the interview

    “I’ll design a large marketplace: about 500 million products and a billion offers, 230,000 product pages a second at a sale peak, and 10 million orders on a normal day. Search, checkout and payments are separate systems with clear contracts.

    I’ll split it into a read side and a write side. On the read side, a page composer calls catalog, offers, stock, delivery promise, reviews and recommendations in parallel. With 20 parallel calls, almost one page in five waits on someone’s p99, so each dependency must be fast at about the 99.95th percentile. I get there with a cacheable product shell, in-memory read models, deadlines, hedged requests and a strict split: optional widgets are dropped when slow; price and the buy button never are, and a price is never invented.

    The catalog stores products as documents checked by product-type schemas, with variants under a parent and offers in a separate table. Seller listings are matched by barcode and model number, and the featured offer is recomputed when inputs change.

    Stock badges and delivery promises come from projections pushed by events, never from inventory directly; pulling them once took Amazon’s own site down. The page may be seconds stale; checkout’s reservation is authoritative. Promises use FC waves, cut-offs and remaining capacity.

    After checkout, the order is a state machine in the orders database. Changes leave through a transactional outbox into Kafka, consumers are idempotent and transitions are conditional, so duplicates and late events are harmless. COD adds a risk check and return-to-origin.

    Seller changes reach every cache and index through CDC into Kafka, keyed by product, with version checks, targeting 5 seconds at p99.

    For Prime Day or Big Billion Days, I pre-scale to 15× so losing a zone still leaves 10×, warm caches, freeze deploys, shed low-priority traffic at the edge, isolate flash deals and run the read side in cells.”

    Concepts used in this design

    References and further reading

    1. Dynamo: Amazon’s Highly Available Key-value Store (DeCandia et al., SOSP 2007)allthingsdistributed.comA page request calls over 150 services; SLAs are set and measured at the 99.9th percentile; aggregator services are stateless and cache heavily.
    2. The Tail at Scale (Dean and Barroso, Communications of the ACM, 2013)cacm.acm.orgFan-out amplifies tail latency (63% of 100-way requests are slow if each server is slow 1% of the time); hedged requests cut a p99.9 from 1,800 ms to 74 ms for 2% extra load.
    3. Caching challenges and strategies (Amazon Builders’ Library)aws.amazon.comServices that become addicted to their cache, soft and hard TTLs, request coalescing, negative caching, and why falling back to the database on a cache outage is dangerous.
    4. Avoiding fallback in distributed systems (Amazon Builders’ Library)aws.amazon.comThe retail outage around 2001 where shipping-speed lookups fell back from cache to the supply-chain database; pushing data proactively instead of pulling it.
    5. Using load shedding to avoid overload (Amazon Builders’ Library)aws.amazon.comGoodput versus throughput, prioritising human traffic over crawlers, shedding as early as possible, and keeping headroom for losing an Availability Zone.
    6. Static stability using Availability Zones (Amazon Builders’ Library)aws.amazon.comOverprovision so each of three zones runs at about 66% of its load-tested capacity, and keep the data plane working when the control plane is impaired.
    7. AWS services scale to new heights for Prime Day 2025: key metrics and milestones (AWS News Blog)aws.amazon.comA four-day event; DynamoDB peaked at 151 million requests/s, ElastiCache at over 1.4 trillion requests in a minute, SQS at 166 million messages/s; over 6,800 fault-injection experiments.
    8. Selling Partner API terminology (Amazon)developer-docs.amazon.comASIN, catalog items reconciled from many sellers’ listings, seller SKUs, variations as child ASINs under a parent, product types, browse nodes and the featured offer.
    9. The Race Against the Clock: Optimizing Supply Chain Capacity for Faster Deliveries (Flipkart Tech Blog)blog.flipkart.techA promise engine using FC waves, cut-offs, pick-to-dispatch times of 30–180 minutes, 6–8 cut-offs a day, spillage, and capacity reserved for cohorts during Big Billion Days.
    10. The Pulse of India’s Supply Chain: How Flipkart Plans for 500 Million Customers (Flipkart Tech Blog)blog.flipkart.techScale figures (150 million products, 1.4 million sellers, 4 million shipments a day, 19,000+ pincodes) and the FC → mother hub → delivery hub flow.