Full Stack • Java • System Design • Cloud • AI Engineering

Amazon E-Commerce System Design — 1 Hour Interview Guide

Design a scalable e-commerce platform like Amazon. Covers product catalog, search ranking, shopping cart, inventory management, checkout with Saga pattern, flash sale handling, order state machine, recommendations, database design, caching, and failure scenarios — all in a 1-hour interview format.

1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 18 min High-level architecture + core components
18 – 26 min Product discovery — search, CDN, caching
26 – 34 min Shopping cart + inventory check
34 – 44 min Checkout flow — Saga pattern, inventory reservation, payment
44 – 51 min Database design + schema
51 – 56 min Flash sales — request queuing, Redis tokens, graceful degradation
56 – 60 min Trade-offs + failure scenarios

What Are We Building?

An e-commerce platform where users can:

  • Browse and search a catalog of 100 million+ products
  • View product details, images, reviews, and pricing
  • Add items to a shopping cart and manage quantities
  • Complete purchases reliably with inventory guarantees
  • Track order status from confirmation through delivery

Scale reference: Amazon serves 300+ million active customers, processes ~1.6 million orders per day, handles 23,000+ product page requests per second on a normal day, and peaks at 10x+ that volume during Prime Day and Black Friday.

Key unique challenges:

  • Overselling prevention — inventory race conditions at flash sale scale (thousands of users per second per item)
  • Read/write asymmetry — 1,000 reads for every 1 write; each path needs separate optimization
  • Checkout reliability — payment + inventory + order creation must be atomic without distributed transactions
  • Flash sales — 10x traffic spike, all concentrated on a handful of hot items

Step 1 — Requirements

Functional Requirements

# Requirement
1 Users can search for products by keyword and filter by category, price, rating
2 Users can view full product details — images, description, specs, reviews, price
3 Users can add, update, and remove items from a shopping cart
4 Cart persists across sessions and across devices for logged-in users
5 System prevents overselling — no user can purchase items that are out of stock
6 Users can complete checkout — inventory reservation, payment, order creation
7 Users can track order status from confirmation to delivery
8 System handles 10x traffic spikes during flash sales and promotional events
9 Sellers can update product details, pricing, and inventory
10 System sends order confirmations and shipping notifications

Non-Functional Requirements

# Requirement
1 Search and product pages load within 200ms (p99)
2 High availability — 99.99% uptime
3 Strongly consistent inventory and orders — overselling is unacceptable
4 Order and payment data must never be lost (durability)
5 System scales to 10x normal traffic during peak events
6 Eventual consistency acceptable for reviews, recommendations, analytics

Out of Scope

  • Seller portal and onboarding
  • Warehouse management and logistics
  • Payment processor internals (assume Stripe/PayPal integration)
  • Returns and refunds processing
  • Fraud detection engine

Step 2 — Capacity Estimation

Assumptions

Metric Value
Daily Active Users (DAU) 100 million
Products in catalog 100 million
Product page views per user/day ~20
Orders per day (2% conversion) 2 million
Cart operations per user/day ~3
Peak traffic multiplier (Prime Day) 10x

Traffic Estimates

Read traffic (product views + search):
  100M DAU × 20 page views / 86,400 sec = ~23,000 QPS average
  Peak (10x):                            = ~230,000 QPS

Write traffic (orders):
  2M orders / 86,400 sec = ~23 QPS average
  Peak (10x):            = ~230 QPS

Cart operations:
  100M DAU × 3 actions / 86,400 sec = ~3,500 QPS

Read : Write ratio ≈ 1,000 : 1

Storage Estimates

Data Type Calculation Storage
Product metadata 100M × 10 KB ~1 TB
Product images 100M × 5 images × 500 KB ~250 TB
Orders (1 year) 2M/day × 2 KB × 365 days ~1.5 TB
User data 500M users × 1 KB ~500 GB

Key Insights

  • 1,000:1 read/write ratio — invest heavily in caching; most requests should never touch the primary database
  • Images dominate storage — product images (~250 TB) live in object storage + CDN, not in the application database
  • Peak traffic is the real challenge — designing for average load causes failures during the most critical moments
  • Orders need special care — low volume but involve multiple services that must coordinate atomically

Step 3 — High-Level Architecture

flowchart TD
    Browser[Web Browser] --> CDN[CDN\nStatic Assets + Images]
    Mobile[Mobile App] --> LB[Load Balancer]
    Browser --> LB

    LB --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> SRCH[Search Service]
    AG --> PROD[Product Service]
    AG --> CART[Cart Service]
    AG --> ORD[Order Service]
    AG --> INV[Inventory Service]

    ORD --> PAY[Payment Service]
    ORD --> KF[Kafka\nOrderCreated]
    KF --> NOTIF[Notification Service]
    KF --> FULFILL[Fulfillment Service]

    SRCH --> ES[(Elasticsearch\nProduct Index)]
    PROD --> REDIS[(Redis\nProduct Cache)]
    PROD --> PDB[(DynamoDB\nProduct Catalog)]
    CART --> CARTDB[(Redis\nCart Store)]
    INV --> INVDB[(PostgreSQL\nInventory)]
    ORD --> ORDDB[(PostgreSQL\nOrders)]
    PAY --> STRIPE[Stripe / PayPal]

Component Responsibilities

Component Responsibility
CDN Serve static assets (images, JS, CSS) from global edge nodes
API Gateway JWT auth, rate limiting, routing
Search Service Keyword search, filters, ranking via Elasticsearch
Product Service Serve product catalog — details, pricing, availability
Cart Service Add/update/remove items; persist across sessions
Inventory Service Track stock levels; reserve during checkout; prevent oversell
Order Service Orchestrate checkout — reserve → pay → create order
Payment Service Charge customer via Stripe/PayPal; handle idempotency
Notification Service Send order confirmation emails and shipping updates
Fulfillment Service Notify warehouse to pick, pack, and ship

Step 4 — Product Discovery

Search Flow

sequenceDiagram
    participant User
    participant AG as API Gateway
    participant SRCH as Search Service
    participant REDIS as Redis Cache
    participant ES as Elasticsearch
    participant CDN

    User->>AG: GET /products/search?q=wireless+headphones&max_price=150
    AG->>SRCH: Forward request
    SRCH->>REDIS: Check cache for query hash
    REDIS-->>SRCH: Cache MISS

    SRCH->>ES: multi-match query + filters + ranking
    ES-->>SRCH: Top 50 ranked results (20ms)
    SRCH->>REDIS: Cache results (TTL: 5 min)
    SRCH-->>User: Product list {id, name, price, rating, thumbnail_url}

    User->>CDN: GET product thumbnail images
    CDN-->>User: Images from nearest edge node

Product Detail Flow

sequenceDiagram
    participant User
    participant PROD as Product Service
    participant REDIS as Redis Cache
    participant DDB as DynamoDB

    User->>PROD: GET /products/{product_id}
    PROD->>REDIS: GET product:{product_id}
    REDIS-->>PROD: Cache HIT (< 1ms) or MISS

    alt Cache MISS
        PROD->>DDB: GetItem by product_id
        DDB-->>PROD: Product document
        PROD->>REDIS: SET product:{product_id} TTL=1hr
    end

    PROD-->>User: Full product {description, images, specs, price, stock_status}

Elasticsearch — What We Index

Each product document in Elasticsearch contains:

{
  "product_id":   "prod_abc123",
  "title":        "Sony WH-1000XM5 Wireless Headphones",
  "description":  "Industry-leading noise cancelling...",
  "brand":        "Sony",
  "category_id":  "electronics/headphones",
  "price":        349.99,
  "avg_rating":   4.7,
  "review_count": 12450,
  "in_stock":     true,
  "sales_30d":    8200,
  "click_rate":   0.082,
  "attributes": {
    "connectivity": "Bluetooth 5.2",
    "battery_life": "30 hours",
    "noise_cancelling": true
  }
}

Search Ranking Formula

final_score =
    BM25(query_text, title, description, brand)  ← text relevance
  × log(sales_30d + 1)                           ← popularity signal
  × click_through_rate                           ← engagement signal
  × business_boost                               ← sponsored / Prime eligible
  × personalization_factor(user_history)         ← user-specific boost
  • BM25 — Elasticsearch's default relevance scorer; title matches weighted 3× over description
  • Popularity — products that sell well are probably correct matches for similar queries
  • Personalization — user's category preferences and purchase history boost relevant results
  • Business rules — sponsored products get a configurable boost; out-of-stock products are demoted

Keeping the Index Fresh

flowchart LR
    PDB[(DynamoDB\nProduct Catalog)] --> CDC[CDC / Debezium\nChange Data Capture]
    CDC --> KF[Kafka\nproduct.updated]
    KF --> IDX[Indexer Service]
    IDX --> ES[Elasticsearch\nProduct Index]
  • CDC (Change Data Capture) watches the database transaction log for product updates
  • Changes flow via Kafka to the Indexer Service which updates Elasticsearch
  • Typical lag: 1–5 seconds — acceptable for price changes to appear in search

CDN Strategy

Asset TTL Reason
Product images 30 days Rarely change after first upload
Category page HTML 5 min Mostly static; price/stock can be stale
Search results 5 min Common queries repeated by many users
Product detail page 1 hour Price and availability update infrequently

Product images are the heaviest asset — 5 images × 500 KB = 2.5 MB per product. Serving 23,000+ images/second from origin is economically and technically infeasible. CDN edge caching is mandatory.


Step 5 — Shopping Cart

Cart Architecture

sequenceDiagram
    participant User
    participant CART as Cart Service
    participant INV as Inventory Service
    participant REDIS as Redis (Cart)
    participant CARTDB as Cart DB (PostgreSQL)

    User->>CART: POST /cart/items {product_id: "prod_abc", quantity: 2}
    CART->>INV: GET /inventory/{product_id}/available
    INV-->>CART: {available_qty: 156, in_stock: true}

    CART->>REDIS: HSET cart:{user_id} prod_abc {"qty":2, "price":349.99}
    REDIS-->>CART: OK (< 1ms)

    CART-->>User: {cart: [{product_id, qty, subtotal}], total: $699.98}

    Note over CART,CARTDB: Async persist for logged-in users (cross-device sync)
    CART->>CARTDB: UPSERT cart_items (async)

Design Decisions

We do NOT reserve inventory when adding to cart. Adding an item to a cart is just a read check. Actual inventory reservation only happens at checkout.

Why? Cart abandonment rate is ~70%. If we reserved inventory at cart-add time, popular items would appear "out of stock" even though most of that reserved stock would be released when users abandon their carts.

Guest carts are stored in Redis with a 48-hour TTL keyed by a session cookie. On login, the guest cart is merged with the user's account cart.

Redis Cart Structure

Key: cart:{user_id}
Type: Hash
Fields:
  {product_id} → {qty, price_at_add, added_at}

TTL: 30 days for logged-in users; 48 hours for guests

Step 6 — Checkout and Order Processing

Full Checkout Sequence (Saga Pattern)

sequenceDiagram
    participant User
    participant AG as API Gateway
    participant ORD as Order Service
    participant INV as Inventory Service
    participant PAY as Payment Service
    participant ORDDB as Order DB
    participant KF as Kafka

    User->>AG: POST /orders {cart_id, address, payment_method}
    AG->>ORD: Create order (idempotency_key: uuid)

    Note over ORD: Step 1 — Reserve Inventory
    ORD->>INV: Reserve items for cart (TTL: 15 min)
    INV-->>ORD: reservation_id (or 409 Out of Stock)

    Note over ORD: Step 2 — Process Payment
    ORD->>PAY: Charge {amount, card_token, idempotency_key}
    PAY-->>ORD: payment_id (or 402 Payment Failed)

    Note over ORD: Step 3 — Create Order Record
    ORD->>ORDDB: INSERT order {status: CONFIRMED, items, payment_id}
    ORDDB-->>ORD: order_id

    Note over ORD: Step 4 — Confirm & Publish
    ORD->>INV: Confirm reservation → deduct stock permanently
    ORD->>KF: Publish order.created {order_id, user_id, items}
    ORD-->>User: 201 {order_id, status: CONFIRMED, eta: "2-3 days"}

    KF-->>NOTIF: Send confirmation email
    KF-->>FULFILL: Notify warehouse to pick and pack

Saga Compensation (Failure Handling)

flowchart TD
    S1[Step 1\nReserve Inventory] -->|success| S2[Step 2\nProcess Payment]
    S2 -->|success| S3[Step 3\nCreate Order Record]
    S3 -->|success| S4[Step 4\nConfirm + Publish]

    S1 -->|out of stock| E1[Return 409\nNo cleanup needed]
    S2 -->|payment failed| C1[Compensation:\nRelease inventory reservation]
    S3 -->|DB write failed| C2[Compensation:\nRefund payment\nRelease inventory]
    S4 -->|Kafka down| LOG[Log event for retry\nOrder is still valid]

Step Action Compensation if Later Step Fails
1 Reserve inventory Release reservation
2 Process payment Refund payment
3 Create order record Mark order as failed (idempotency prevents duplicate charges)
4 Publish events Retry — order is already confirmed; events are non-critical

Order State Machine

stateDiagram-v2
    [*] --> PENDING : POST /orders received
    PENDING --> CONFIRMED : Payment + inventory succeed
    PENDING --> CANCELLED : Payment failed or out of stock
    CONFIRMED --> PROCESSING : Warehouse picks items
    CONFIRMED --> CANCELLED : User cancels before processing
    PROCESSING --> SHIPPED : Package dispatched
    SHIPPED --> DELIVERED : Package received
    DELIVERED --> RETURNED : Customer initiates return
    RETURNED --> [*]
    CANCELLED --> [*]

Idempotency — Preventing Double Charges

Every checkout request carries a client-generated idempotency key:

POST /orders
Idempotency-Key: a3f7b2d1-9c4e-4a8b-b3d2-7f1c2e3a4b5c

{
  "cart_id": "cart_user123",
  "shipping_address_id": "addr_456",
  "payment_method_id": "pm_789"
}

Server logic:

  1. Check if idempotency_key already exists in the database
  2. If yes → return the cached response from the first request (no duplicate processing)
  3. If no → process the order and store the result with the key

A network timeout may cause the client to retry, but with the same idempotency key, the server returns the first response — the customer is never double-charged.


Step 7 — Database Design

Database Selection

Data Database Reason
Product catalog DynamoDB Flexible schema per category; high read throughput
Product search index Elasticsearch Full-text + filters + ranking; millisecond queries
Shopping carts Redis Sub-ms ops; session-scoped; small data per key
Inventory PostgreSQL ACID row-level locking; strong consistency required
Orders + payments PostgreSQL ACID transactions; financial data; predictable schema
Product metadata cache Redis 90%+ cache hit rate; reduces DynamoDB reads

Product Table (DynamoDB)

{
  "product_id":    "prod_abc123",
  "seller_id":     "seller_456",
  "category_id":   "electronics/headphones",
  "title":         "Sony WH-1000XM5",
  "price":         349.99,
  "currency":      "USD",
  "avg_rating":    4.7,
  "review_count":  12450,
  "images":        ["s3://bucket/prod_abc/img1.jpg", "..."],
  "attributes": {
    "brand": "Sony",
    "connectivity": "Bluetooth 5.2",
    "battery_hours": 30,
    "noise_cancelling": true,
    "weight_grams": 250
  },
  "status":        "ACTIVE",
  "created_at":    "2024-01-15T10:00:00Z"
}

Access patterns:

  • GetItem by product_id — primary key lookup (O(1))
  • List by category_id — GSI on category_id + created_at
  • List by seller_id — GSI on seller_id

Inventory Table (PostgreSQL)

Table: inventory

  product_id     VARCHAR(50)    PK (composite)
  warehouse_id   VARCHAR(50)    PK (composite)
  available_qty  INTEGER        NOT NULL CHECK (available_qty >= 0)
  reserved_qty   INTEGER        NOT NULL DEFAULT 0
  version        INTEGER        NOT NULL DEFAULT 0   ← optimistic locking
  updated_at     TIMESTAMP

PRIMARY KEY (product_id, warehouse_id)

Optimistic locking on update:

-- Reserve 2 units — only succeeds if version matches (no concurrent modification)
UPDATE inventory
SET    available_qty = available_qty - 2,
       reserved_qty  = reserved_qty  + 2,
       version       = version + 1
WHERE  product_id    = 'prod_abc123'
  AND  warehouse_id  = 'wh_east'
  AND  available_qty >= 2
  AND  version       = 7;            -- must match current version

-- If 0 rows affected → another transaction won; retry or fail

Orders Table (PostgreSQL)

Table: orders

  order_id            VARCHAR(50)    PRIMARY KEY
  user_id             VARCHAR(50)    NOT NULL  FK → users
  status              VARCHAR(20)    PENDING | CONFIRMED | PROCESSING | SHIPPED | DELIVERED | CANCELLED
  subtotal            DECIMAL(10,2)
  tax                 DECIMAL(10,2)
  shipping_fee        DECIMAL(10,2)
  total               DECIMAL(10,2)
  shipping_address_id VARCHAR(50)
  payment_id          VARCHAR(50)    (Stripe payment intent ID)
  idempotency_key     VARCHAR(100)   UNIQUE    ← duplicate prevention
  created_at          TIMESTAMP
  updated_at          TIMESTAMP

Order Items Table (PostgreSQL)

Table: order_items

  order_item_id  VARCHAR(50)    PRIMARY KEY
  order_id       VARCHAR(50)    FK → orders
  product_id     VARCHAR(50)
  product_name   VARCHAR(255)   ← denormalized: price/name at purchase time
  quantity       INTEGER
  unit_price     DECIMAL(10,2)  ← price at purchase time, not current price
  subtotal       DECIMAL(10,2)

INDEX: order_id (for fetching all items of an order)

product_name and unit_price are deliberately denormalized — order history must show what the customer paid, even if the product is later renamed, repriced, or deleted.


Step 8 — Caching Strategy

Redis Cache Map

Cache Key Pattern TTL Contents
Product detail product:{product_id} 1 hr Full product document
Search results search:{query_hash} 5 min Top 50 results for this query + filters
Inventory (read) inv:avail:{product_id} 30 sec Available quantity (approximate)
User cart cart:{user_id} 30 days Hash of product_id → qty, price
Guest cart cart:guest:{session_id} 48 hrs Same structure as user cart
Flash sale token sale:{sale_id}:tokens Sale TTL Redis Set of purchase tokens
Session / auth session:{user_id} 30 days JWT + device info

Read-Through Cache for Products

Request arrives for product_id:
  1. Check Redis → HIT? Return in < 1ms
  2. MISS → Query DynamoDB (10–20ms)
  3. Write result to Redis with 1-hour TTL
  4. Return to client

Cache invalidation:
  When seller updates product → Kafka event → invalidate Redis key
  Next request repopulates the cache

With a 90%+ cache hit rate, DynamoDB only sees ~2,300 QPS instead of 23,000 QPS — a 10× reduction in database load.


Step 9 — Inventory Management and Preventing Overselling

Four approaches, from simplest to most scalable:

Approach 1 — Pessimistic Locking

-- Lock the row before reading + updating
BEGIN;
SELECT available_qty FROM inventory
WHERE  product_id = 'prod_abc'
FOR UPDATE;                           -- acquires exclusive row lock

UPDATE inventory
SET    available_qty = available_qty - 1
WHERE  product_id = 'prod_abc'
  AND  available_qty >= 1;

COMMIT;

✅ Guarantees no overselling ❌ Creates contention under load — all concurrent requests queue on the same lock

Approach 2 — Optimistic Locking (Default Choice)

UPDATE inventory
SET    available_qty = available_qty - 1,
       version       = version + 1
WHERE  product_id    = 'prod_abc'
  AND  available_qty >= 1
  AND  version       = :current_version;

-- If 0 rows affected → another transaction won → retry

✅ No blocking; high throughput when conflicts are rare ❌ High retry rates during flash sales when all users hit the same row

Approach 3 — Reservation Pattern with TTL (Best for Checkout)

-- At checkout start: move qty from available to reserved (10-min TTL)
UPDATE inventory
SET    available_qty = available_qty - :qty,
       reserved_qty  = reserved_qty  + :qty
WHERE  product_id    = :product_id
  AND  available_qty >= :qty;

INSERT INTO reservations (reservation_id, product_id, qty, expires_at)
VALUES (:uuid, :product_id, :qty, NOW() + INTERVAL '10 minutes');

-- Background job: release expired reservations every 60 seconds
UPDATE inventory i
SET    available_qty = i.available_qty + r.qty,
       reserved_qty  = i.reserved_qty  - r.qty
FROM   reservations r
WHERE  r.product_id = i.product_id
  AND  r.expires_at < NOW();

DELETE FROM reservations WHERE expires_at < NOW();

✅ Fair (first-come, first-served); users know item is held for them ❌ Requires background cleanup job; TTL tuning matters

Approach 4 — Redis Atomic Counter (Flash Sales)

# Pre-load inventory into Redis before flash sale starts
SET inventory:flash:prod_abc 1000

# Each purchase attempt — atomic decrement
DECR inventory:flash:prod_abc
# Returns new count. If < 0 → sold out; rollback the decrement
# INCR inventory:flash:prod_abc  (undo)

✅ Sub-millisecond, handles 100K+ ops/sec on a single Redis instance, atomic — no race conditions ❌ Redis is not durable by default; requires sync back to PostgreSQL; only practical for pre-planned hot items

Which Approach to Use?

Scenario Approach
Normal shopping (most products) Optimistic locking with version
Limited stock items Reservation pattern with TTL
Pre-announced flash sale (known inventory) Redis atomic counter
Mixed traffic Optimistic by default; Redis for flagged "hot" items

Step 10 — Flash Sale Handling

Flash sales are the ultimate stress test — traffic can spike from 23,000 QPS to 230,000 QPS in seconds, all concentrated on a handful of hot items.

Multi-Layered Defense

flowchart TD
    USERS[Burst of Users\n230K req/sec] --> CDN[Layer 1: CDN\nCache product pages\n30s TTL for stock status]
    CDN --> RL[Layer 2: Rate Limiting\n10 req/sec per user\n5 req/sec per IP]
    RL --> QUEUE[Layer 3: Request Queue\nKafka — buffer checkout requests]
    QUEUE --> WORKERS[Layer 4: Order Workers\nProcess at controlled rate]
    WORKERS --> REDIS[Layer 5: Redis Token Check\nPre-generated purchase tokens]
    REDIS -->|token available| ORDER[Create Order\nPostgreSQL]
    REDIS -->|no token| SOLD[Return 410 Sold Out]

Strategy 1 — Request Queuing

Enable "queue mode" for hot products when a flash sale starts:

1. Incoming purchase requests → Kafka topic (not directly to Order Service)
2. Worker pool consumes from queue at a controlled rate (e.g., 500 orders/sec)
3. Users see real-time position: "You are #1,234 in line. Est. wait: 3 min."
4. Workers process orders → PostgreSQL sees only 500 writes/sec, not 230K

The database sees only successful, controlled traffic — not the raw burst. First users to click are first served.

Strategy 2 — Pre-Computed Inventory Tokens

For planned sales with known inventory (e.g., 1,000 units of a limited edition):

# Before the sale: pre-generate exactly 1,000 tokens
SADD sale:flash123:tokens token_1 token_2 ... token_1000

# At checkout: atomically pop a token
SPOP sale:flash123:tokens
# Returns token → proceed to payment
# Returns nil → sold out (return 410)

Physically cannot oversell — there are exactly as many tokens as items. O(1), handles any concurrency.

Strategy 3 — CDN + Static Caching

  • Cache the product page at CDN edge with a 30-second TTL for stock status
  • Show approximate counts: "Almost sold out!" instead of "3 remaining" for 29 of every 30 seconds
  • Absorbs 99%+ of page view traffic at the edge — never reaches the origin

Strategy 4 — Graceful Degradation

When the system is under extreme load, shed non-essential work:

Feature Under Normal Load Under Flash Sale Load
Personalized recs Fully computed Show static "popular items"
Search results Fresh from ES Serve cached (potentially stale)
Product reviews Full, paginated Summarized count + avg rating only
Analytics events Real-time Batched, delayed
Recommendation refresh Per-session Hourly batch only

The core purchase flow (search → add to cart → checkout) stays fully functional and responsive.


Step 11 — Recommendation System

Amazon attributes 35% of its revenue to "Customers who bought this also bought..." widgets.

Pipeline Architecture

flowchart TD
    EVENTS[User Events\nviews, purchases, searches] --> KF[Kafka]
    KF --> EP[Event Processor]
    EP --> FS[(Feature Store\nUser + Item vectors)]
    FS --> ML[ML Training\nCollaborative + Content filtering]
    ML --> MS[(Model Store\nEmbeddings)]

    REC_REQ[Product Page\nRequest] --> RS[Recommendation Service]
    RS --> REDIS2[(Redis Cache\nPre-computed recs)]
    REDIS2 -->|cache hit| RESULT[Return recommendations]
    RS -->|cache miss| MS
    MS --> RERANK[Re-rank by\nuser session context]
    RERANK --> REDIS2
    RERANK --> RESULT

    style KF fill:#ede9fe,stroke:#7c3aed
    style REDIS2 fill:#fff4e0,stroke:#f59e0b

Recommendation Types

Widget Signal Where
Frequently bought together Co-purchase graph (items in same order) Product page
Customers also viewed Co-view graph (same session) Product page
Based on your history User purchase + view embeddings Homepage
Trending in category Sales count last 24 hours Category page
Recently viewed User's own session history Sidebar

Algorithm Approaches

Approach Mechanism Weakness
Collaborative filtering "Users like you also bought Y" Cold start for new items/users
Content-based filtering "Similar attributes to items you liked" Filter bubble effect
Hybrid (production) Deep learning embeddings (user + item) Needs large training data

Performance: Recommendations appear on high-traffic pages — cache them aggressively. Pre-compute for popular products. Load asynchronously (AJAX after page render). Graceful fallback to trending items if personalization times out.


Step 12 — Scaling

Read Path Scaling

flowchart LR
    23K_QPS[23,000 QPS reads] --> CDN[CDN\n~70% hit rate on images]
    CDN --> AG[API Gateway]
    AG --> REDIS[Redis Cache\n~90% hit rate on products]
    REDIS -->|10% miss| DYNAMO[DynamoDB\n~2,300 QPS]
    DYNAMO --> REPLICA[DynamoDB\nRead Replicas\nmulti-region]
  • CDN absorbs all image requests (~70% of total bytes)
  • Redis absorbs 90% of product metadata requests
  • DynamoDB only sees ~2,300 QPS — well within its capacity

Write Path Scaling

  • Order Service is stateless — scale horizontally with pods
  • PostgreSQL orders table: range partition by created_at — current month = hot partition; older months archived
  • Read replicas for order history queries (GET /orders/{id})
  • Kafka partitioned by user_id — all events for one user processed in order

Flash Sale Auto-Scaling

Pre-flash sale preparation (T-30 min):
  1. Increase CDN cache TTL on product pages: 5 min → 60 sec
  2. Pre-load product inventory into Redis counters
  3. Increase API Gateway rate limits slightly (authenticated users: 20 req/sec)
  4. Spin up 2x Order Service pods pre-emptively
  5. Enable "queue mode" for the specific product_id

Post-flash sale (T+2 hours):
  1. Auto-scale down pods based on traffic metrics
  2. Flush Redis tokens; sync final inventory count to PostgreSQL
  3. Resume normal CDN TTLs

Step 13 — Failure Scenarios

Failure Impact Mitigation
Redis cache down Cache miss storm hits DynamoDB Redis Sentinel failover; circuit breaker caps DynamoDB blast radius
Elasticsearch down Search returns 503 Fallback to DynamoDB-based category browse; show "Search unavailable"
Payment Service timeout Checkout stalls Idempotency key + retry; client retries get cached response
Order DB primary down Checkout writes fail Automatic PostgreSQL failover < 60s; orders queued in Kafka
Kafka down Order events not published Order is still confirmed; events in dead letter queue; retry on recovery
Inventory over-decrement Negative stock CHECK (available_qty >= 0) constraint; floor at zero
Flash sale token exhaustion Sold out SPOP returns nil → instant 410 response; never reaches order system
DynamoDB throttling Product detail pages slow Exponential backoff + Redis cache; most reads hit Redis anyway
Fulfillment Service down Orders not picked Kafka retains events; Fulfillment picks up on recovery

Final Architecture

flowchart TD
    CLIENTS[Web + Mobile Clients] --> CDN[CDN\nImages + Static Assets]
    CLIENTS --> LB[Load Balancer]
    LB --> AG[API Gateway\nAuth + Rate Limiting]

    AG --> SRCH[Search Service]
    AG --> PROD[Product Service]
    AG --> CART[Cart Service]
    AG --> ORD[Order Service]
    AG --> INV[Inventory Service]

    ORD --> PAY[Payment Service]
    ORD --> KF[Kafka Cluster]
    INV --> KF

    KF --> NOTIF[Notification Service]
    KF --> FULFILL[Fulfillment Service]
    KF --> IDX[Search Indexer]

    SRCH --> ES[(Elasticsearch)]
    IDX --> ES
    PROD --> REDIS[(Redis Cluster\nProduct + Cart Cache)]
    CART --> REDIS
    PROD --> DDB[(DynamoDB\nProduct Catalog)]
    INV --> PG[(PostgreSQL\nInventory + Orders)]
    ORD --> PG
    PAY --> STRIPE[Stripe / PayPal]

    style CDN fill:#fef3c7,stroke:#d97706
    style KF fill:#ede9fe,stroke:#7c3aed
    style REDIS fill:#fff4e0,stroke:#f59e0b
    style ES fill:#e8f0fe,stroke:#3b82d4
    style PG fill:#f0fdf4,stroke:#16a34a

Technology Stack

Layer Technology
CDN CloudFront / Akamai
Load Balancer AWS ALB / NGINX
API Gateway AWS API Gateway / Envoy + JWT auth
Product catalog DynamoDB (flexible schema, high read throughput)
Product search Elasticsearch (full-text + filters + ranking)
Cart store Redis Cluster (hash per user)
Inventory + Orders PostgreSQL (ACID, row-level locking)
Product image storage S3 + CloudFront
Caching layer Redis Cluster (product metadata, search results)
Event streaming Apache Kafka (RF=3)
CDC (search sync) Debezium → Kafka → Indexer Service
Payment Stripe / PayPal gateway
Recommendation ML Apache Spark + TensorFlow + FAISS
Monitoring Prometheus + Grafana + Jaeger
Deployment Kubernetes multi-region (AWS)

Key Trade-Offs

Decision Option A Option B Choice and Reason
Product catalog DB PostgreSQL DynamoDB DynamoDB — flexible per-category schema; 23K reads/sec; horizontal scale
Inventory locking Pessimistic Optimistic + Redis for hot items Optimistic for normal; Redis counter for flash sales — best of both worlds
Cart reservation Reserve at cart-add Check only, reserve at checkout Check only — 70% cart abandonment makes early reservation wasteful
Flash sale buffering Direct to Order Service Kafka request queue Kafka queue — protects Order Service and PostgreSQL from burst traffic
Search indexing Synchronous (block writes) Async via CDC + Kafka Async CDC — product writes are not blocked by Elasticsearch indexing latency
Checkout atomicity Distributed transaction Saga with compensations Saga — payment processors don't support 2PC; Saga provides eventual consistency
Order history DynamoDB PostgreSQL PostgreSQL — orders need ACID, joins (items), and strong consistency

Common Interview Mistakes

  • ❌ Using PostgreSQL for the product catalog — 100M products with varied attributes needs flexible schema (DynamoDB/MongoDB)
  • ❌ Reserving inventory at cart-add time — 70% cart abandonment causes most items to appear falsely "out of stock"
  • ❌ Synchronous payment blocking checkout — use async Kafka flow; idempotency keys prevent double charges on retry
  • ❌ No Saga pattern for checkout — distributed transactions across DB + payment processor are not possible
  • ❌ No inventory reservation TTL — abandoned checkouts permanently block stock without an expiry mechanism
  • ❌ Single database for everything — inventory needs ACID PostgreSQL; products need flexible DynamoDB; caching needs Redis
  • ❌ No flash sale strategy — 10x spike with request concentration requires queuing, Redis tokens, and rate limiting
  • ❌ Not mentioning CDN — product images (250 TB) must be served from CDN edges; origin delivery is infeasible
  • ❌ No idempotency keys — network retries cause double charges without them
  • ❌ Real-time recommendations — pre-compute and cache for popular products; real-time ML per request is too slow

Interview Questions

  1. How do you prevent overselling when 10,000 users all try to buy the last 100 units simultaneously?
  2. What is the Saga pattern and why is it used for checkout instead of a distributed transaction?
  3. Why use DynamoDB for the product catalog instead of PostgreSQL?
  4. How does the inventory reservation TTL work, and what happens when it expires?
  5. How do you handle a network timeout during checkout that causes the client to retry?
  6. How does Elasticsearch stay in sync with the product database? What is CDC?
  7. Walk me through the complete flow when a user clicks "Place Order."
  8. Why not reserve inventory when the user adds an item to their cart?
  9. How would you design the system to handle a flash sale with 230,000 QPS?
  10. What is the pre-computed token approach for flash sales and what are its trade-offs?
  11. How does search ranking work? What signals does it use beyond text relevance?
  12. How do you ensure the cart is consistent across devices for logged-in users?
  13. What happens if payment succeeds but the Order DB write fails?
  14. Why is unit_price stored in the order_items table instead of looking it up from the product?
  15. How would you scale the Order Service to handle 10x traffic during Prime Day?

Summary

Concern Solution
Product catalog DynamoDB — flexible schema per category; high read throughput
Product search Elasticsearch with BM25 + popularity + personalization scoring
Search freshness CDC (Debezium) → Kafka → Indexer → Elasticsearch (1–5s lag)
CDN strategy 30-day TTL for images; 5-min TTL for search results
Shopping cart Redis hash per user — sub-ms ops; async persist for cross-device sync
Inventory locking Optimistic locking (normal); Redis atomic counter (flash sales)
Inventory reservation Reserve at checkout with 15-min TTL; background job releases expired holds
Checkout reliability Saga pattern — each step has a compensating action on failure
Double charge prevention Idempotency keys — same key returns cached result, never reprocesses
Flash sale scaling CDN caching + rate limiting + Kafka queue + Redis tokens + graceful degradation
Order history PostgreSQL with ACID; denormalized price + name at purchase time
Recommendations Offline batch (Spark/TF) + online re-rank; cached in Redis per product
Scale CDN + Redis cache 90%+ of reads; stateless services; Kafka async processing

The core principle: Amazon is a read-heavy platform with an extreme write-safety requirement. Cache aggressively for reads (CDN + Redis absorb 90%+ of traffic). For writes, the inventory → payment → order sequence must be bulletproof — the Saga pattern with compensations and idempotency keys is the correct answer. Flash sales require a completely separate strategy: queue, token, and degrade.