Case Study 1: Amazon / Flipkart E-commerce
Design an online store where tens of millions of people search products, fill carts, pay and track orders, without crashing even during a flash sale.
Read every case study in the standard interview format. The 8 steps are:
- Requirements: first ask what the system will do (functional) and how fast and available it must be (non-functional).
- Estimation: users, QPS, storage, bandwidth. The numbers tell you how big the system must be.
- API: which endpoints exist between client and server.
- Data model: which tables/collections, in which database, sharded on which key.
- High-level architecture: a diagram with users on top, then services, data and workers below.
- Flows: the step-by-step journey of the main request.
- Deep dives: the hardest problems and their solutions. This earns the most marks in an interview.
- Trade-offs: what you chose and why, and what you left out.
C.1 Requirements
Functional requirements:
- Product catalog: a seller adds products; users see details, images, price and reviews.
- Search and filter: by keyword, category, brand, price range and rating; autocomplete suggestions.
- Cart: add/remove items, change quantity; the cart stays in sync across all devices after login.
- Checkout: select address, apply coupon, pay (UPI, card, COD) and confirm the order.
- Inventory: stock count per warehouse; reserve stock on order, release it back on cancel.
- Flash sale: sell limited stock (for example 10,000 phones) at a fixed time among lakhs of users.
- Order tracking: placed, packed, shipped, out for delivery, delivered statuses and notifications (SMS/email/push).
Non-functional requirements:
- Availability 99.99% for browse and search; high availability for checkout too, but correctness comes first.
- Latency: product page and search p99 < 200 ms; checkout p99 < 1 second.
- Consistency: inventory and payment strongly consistent (no overselling); catalog and reviews can be eventually consistent.
- Scale: 10 crore (100 million) DAU, normal peak 50K read QPS, flash-sale spike of 10 lakh+ (1 million+) requests/second.
- Durability: no confirmed order or payment may ever be lost (replicated DB + Kafka).
C.2 Estimation (back-of-the-envelope)
| Item | Number | How it is derived |
|---|---|---|
| Daily active users | 10 crore | Assumption: India-scale e-commerce, 2-3x during a festival sale. |
| Read QPS (browse/search) | ~25K avg, 75K peak | 10 crore users x 20 page views = 200 crore/day; / 86,400 = ~23K QPS; peak 3x. |
| Orders per day | 1 crore | 10% of users order; 1 crore / 86,400 = ~115 orders/sec on average, ~5K/sec at sale peak. |
| Order storage | ~7 TB/year | One order is ~2 KB (items, address, amount); 1 crore x 2 KB = 20 GB/day; x 365 = ~7.3 TB/year, with 3x replication = ~22 TB. |
| Catalog size | ~5 TB metadata | 50 crore products x 10 KB metadata = 5 TB; images live separately in object storage (S3) + CDN. |
| Image bandwidth | ~100 Gbps avg | 23K page views/sec x ~500 KB images = ~11.5 GB/s = ~92 Gbps; the CDN serves 95%+ of this, so only ~5 Gbps reaches the origin. |
| Cache size (hot products) | ~200 GB Redis | Top 2 crore hot products x 10 KB = 200 GB; a Redis cluster of 10 nodes x 32 GB. |
C.3 API design
| Endpoint | Purpose |
|---|---|
| GET /v1/products/{id} | Product details, price, stock status (from cache) |
| GET /v1/search?q=phone&brand=x&page=1 | Search results from Elasticsearch, with filters and pagination |
| POST /v1/cart/items | Add an item to the cart {product_id, qty} |
| POST /v1/orders | Place an order (with an Idempotency-Key header so no double order is created) |
| POST /v1/payments/callback | Payment gateway webhook; verify the signature, then confirm the order |
| GET /v1/orders/{id}/tracking | Current order status and shipment events |
C.4 Data model
| Table / Store | Fields | Why |
|---|---|---|
| products (MySQL/Postgres + Elasticsearch copy) | product_id PK, seller_id, title, category, price, attributes JSON, updated_at | Source of truth in SQL; synced to Elasticsearch via CDC for search. |
| inventory (MySQL, sharded by product_id) | product_id, warehouse_id, available_qty, reserved_qty, version | Stock needs strong consistency and row-level locking. |
| cart (Redis + DynamoDB/Cassandra) | user_id PK, items list {product_id, qty, added_at} | Fast reads/writes, key-value access pattern, lookup only by user_id. |
| orders (MySQL, sharded by user_id) | order_id PK, user_id, status, amount, address_id, idempotency_key UNIQUE, created_at | Needs ACID transactions; a user's order history lands in a single shard. |
| payments (MySQL) | payment_id PK, order_id, gateway_txn_id, status, amount | Record of money: strict consistency and audit. |
| shipment_events (Cassandra) | order_id PK, event_time CK, status, location | Append-heavy time-series data: fast writes and range reads by order_id. |
C.5 High-level architecture
- 1.Clients: Mobile App and Web Browser
- 2.Edge: DNS, CDN (images, JS/CSS, S3 origin) and WAF (bot / DDoS)
- 3.Load Balancer -> API Gateway (JWT auth, rate limit, routing)
- 4.Services: Catalog, Search, Cart, Order, Payment, Inventory, Notification, Tracking
- 5.Data: Redis Cluster (200 GB hot products, cart, stock), sharded MySQL (orders, inventory, payments), Elasticsearch (products index), Cassandra (shipment_events, cart copy), S3 (product images)
- 6.Outbox / CDC (Debezium) -> Kafka: OrderPlaced, PaymentSuccess, Shipped
- 7.Workers: Search Indexer (CDC -> ES update), Email/SMS Worker, Analytics (sales, funnels), Order Timeout (10 min -> CANCEL, stock release)
Checkout path: App -> WAF/LB -> Gateway -> Order -> Inventory reserve -> MySQL PENDING_PAYMENT -> Payment Gateway (Razorpay / PayU) -> webhook -> Kafka. Numbers: 10 crore DAU, 75K peak read QPS, flash sale 10 lakh+ rps, 1 crore orders/day.
- Static images/JS come from the CDN (origin S3); API requests go WAF -> Load Balancer -> API Gateway (JWT + rate limit) to the microservices.
- Read path: Catalog uses Redis cache-aside, MySQL on a miss; Search runs on Elasticsearch; Cart lives in Redis with a durable copy in Cassandra.
- Checkout: Order Service checks idempotency, Inventory reserves with a 10 min TTL, and the order is saved as PENDING_PAYMENT + outbox in MySQL.
- After the gateway webhook is verified, PaymentSuccess goes on Kafka; workers send SMS/email, update ES, and release stock on timeout.
| Component | What it does |
|---|---|
| CDN | Serves product images and JS/CSS static files from the edge; origin load drops by 90%. |
| API Gateway | Auth (JWT), rate limiting per user/IP, routing to microservices. |
| Catalog Service | Serves product details; Redis cache-aside, reads MySQL on a miss. |
| Search Service | Full-text search, filters and ranking on Elasticsearch; prefix index for autocomplete. |
| Cart Service | Keeps the cart in Redis with a durable copy in DynamoDB/Cassandra; the guest cart is merged on login. |
| Inventory Service | Reserves/releases stock; uses a Redis atomic counter (Lua script) for flash sales. |
| Order Service | Creates orders with an idempotency key and coordinates payment and inventory via a Saga. |
| Payment Service | Talks to the gateway (Razorpay/PayU type), verifies webhooks, retries with idempotency. |
| Kafka | Carries events like OrderPlaced, PaymentSuccess, Shipped; consumers are notification, search indexer, analytics. |
| Tracking Service | Takes updates from courier partners, writes them to shipment_events and pushes to the user. |
C.6 Main flows
Order placement flow (checkout):
- The user taps "Place Order"; the app sends POST /v1/orders with a unique Idempotency-Key (UUID).
- The API Gateway verifies the JWT, checks the rate limit and routes the request to Order Service.
- Order Service checks the idempotency key; if an order already exists it returns the same response.
- Order Service calls Inventory Service to reserve stock (reserved_qty += qty, 10 min TTL).
- The order row is saved in MySQL with status PENDING_PAYMENT, with an outbox event in the same transaction.
- The user is redirected via Payment Service to the gateway and pays by UPI/card.
- The gateway sends a webhook; Payment Service verifies the signature and publishes PaymentSuccess to Kafka.
- Order Service sets status CONFIRMED; Inventory converts reserved stock to sold.
- A notification worker reads the event from Kafka and sends SMS/email/push; a fulfilment event goes to the warehouse.
- If payment does not arrive within 10 min, the timeout worker sets the order to CANCELLED and releases the stock (compensating action).
Product search flow:
- The user types "iphone" in the search box; the autocomplete API is called every few keystrokes (debounce).
- The request reaches Search Service via CDN/LB; results of popular queries are cached in Redis for 1-5 min.
- On a cache miss, Search Service builds an Elasticsearch query: text match + filters + ranking (sales, rating).
- Elasticsearch shards search in parallel and the top results are merged.
- Search Service enriches the product IDs with price and stock from the Catalog/Inventory cache.
- The response carries product cards and image URLs (CDN); images load from the CDN.
Flash sale flow:
- Before the sale the stock count is loaded into Redis (for example sale:phoneX = 10000) and pages are pre-warmed on the CDN.
- When the sale starts, lakhs of users tap "Buy"; the API Gateway applies a per-user rate limit and bot detection.
- Requests go into a virtual waiting room/queue so traffic reaches the backend only at a controlled rate.
- Inventory Service does an atomic DECR through a Redis Lua script; if the result is >= 0 the user gets a token, otherwise sold out.
- The winners' order requests go onto Kafka; Order Service consumes at its own speed and creates the orders.
- If the user does not pay within the payment window (5-10 min), the Redis counter is INCR'd and the stock returns to the pool.
C.7 Deep dives
- 1.App: POST /orders + Idempotency-Key -> API Gateway: JWT + rate limit
- 2.Order Svc: key already seen? If duplicate, return the same response
- 3.Inventory: reserve qty (10 min TTL); Order: PENDING_PAYMENT + outbox
- 4.User redirected -> UPI / card pay -> gateway webhook ok
- 5.Payment: PaymentSuccess on Kafka -> Order CONFIRMED -> Inventory reserved to sold
- 6.SMS / email / push + warehouse fulfilment event
- 7.No payment in 10 min: Timeout Worker -> order CANCELLED + stock release (compensating action)
Order, Inventory and Payment are separate services, so the distributed transaction runs as a Saga.
- 1.Pre-load: sale:phoneX = 10000; pages pre-warmed on CDN
- 2.Buyers 10 lakh+ rps -> API Gateway: per-user limit + bot check
- 3.Waiting room: controlled rate
- 4.Redis Lua script, atomic: n = DECR sale:phoneX; n >= 0 gets a token, n < 0 sold out
- 5.Winner -> Kafka -> Order Svc consumes at its own speed
- 6.Did not pay: after 5-10 min INCR, stock goes back to the pool
- 1.App: req 1 key abc, req 2 key abc (double click / retry)
- 2.Order Svc: starts the Saga; orders in MySQL with idempotency_key UNIQUE
- 3.1st: INSERT creates the order; 2nd: duplicate, same response returned
- 4.The same key goes to the Payment Gateway too, so money is deducted only once
Problem: overselling in a flash sale (10,000 stock, 10 lakh buyers). If everyone hits a DB row lock the DB will die. So check-and-decrement is done with an atomic Lua script in Redis, and orders are then written to MySQL asynchronously via Kafka. The DB also keeps an available_qty >= 0 constraint as a safety net.
Problem: double order or double payment (retry, double click). The client sends an Idempotency-Key on every checkout; the orders table has a UNIQUE constraint. The same key is sent to the payment gateway, so retries are safe and money is not deducted twice.
Problem: distributed transaction across separate order, inventory and payment services. 2PC is slow, so use the Saga pattern. Every step has a compensating action: if payment fails, release stock and cancel the order. The outbox pattern keeps the DB write and the Kafka event consistent.
Problem: keeping the search index in sync with the catalog. CDC (Debezium) on the catalog DB sends changes to Kafka and the Search Indexer updates Elasticsearch. A lag of a few seconds is acceptable (eventual consistency).
Problem: cache stampede on a hot product. When a sale product's cache expires, thousands of requests must not hit the DB, so use request coalescing/locks, jittered TTLs, and keep hot keys in a local in-memory cache too.
Problem: payment gateway slow or down. Add a circuit breaker and keep multiple gateways; on failure, route to another gateway. For pending payments a reconciliation job polls the gateway for status.
C.8 Trade-offs
| Decision | What was chosen and why |
|---|---|
| SQL vs NoSQL for orders | MySQL for orders and payments (ACID, sharded by user_id). NoSQL for cart and tracking events because they have simple key access and heavy writes. |
| Strong vs eventual consistency | Strong for inventory and payment; eventual for catalog, reviews, search. Making everything strong raises both latency and cost. |
| Stock reserve at add-to-cart vs at checkout | Reserve at checkout; reserving in the cart lets people block stock and walk away. The cart only shows an "in stock" hint. |
| Sync vs async order processing | On a normal day the flow is sync; in a flash sale it is async through a Kafka queue and the user sees "order processing". Throughput rises, with a little delay. |
| Microservices vs monolith | Microservices at large scale so search and checkout can scale separately. For a small team, starting with a modular monolith is better. |
C.9 Interview tips
- Clarify scope first: not all of Amazon, focus on 2-3 core features (search, cart, checkout).
- Treat the read-heavy (browse) and write-critical (checkout) paths separately; explain caching and consistency for each.
- Always cover overselling and double payment: Redis atomic decrement, idempotency key, Saga.
- Talk with numbers: QPS, storage, cache size; this convinces the interviewer the design is grounded.
- Cover failure scenarios: payment gateway down, Kafka lag, cache failure, region outage.