1. Requirements & Scope (5 min)

Functional Requirements

  1. Add, remove, and update quantity of items in the cart (CRUD operations with real-time inventory validation)
  2. Support both guest carts (cookie/session-based) and authenticated user carts, with automatic cart merging when a guest logs in
  3. Persist carts durably across sessions, devices, and app restarts — a user who adds an item on mobile must see it on desktop
  4. Handle price and availability changes while items sit in the cart — show current price, flag out-of-stock items, and surface price-change notifications
  5. Transition the cart atomically to checkout — reserve inventory, lock prices, and create an order in a single coordinated operation

Non-Functional Requirements

  • Availability: 99.99% uptime. Cart unavailability directly equals lost revenue. Even 1 minute of cart downtime during Prime Day can cost millions.
  • Latency: Add-to-cart < 100ms p99. Cart read (render page) < 50ms p99. These must hold during 10x traffic spikes.
  • Consistency: Eventual consistency for cart reads (stale by at most 1-2 seconds). Strong consistency for checkout transition (inventory decrement must be atomic).
  • Scale: 300M+ active users, 500M+ carts (including guest and abandoned), 50K add-to-cart operations/sec average, 500K/sec peak during Prime Day.
  • Durability: Zero cart data loss. A cart is a purchase intent — losing a cart with 15 items a user spent 30 minutes curating is unacceptable.

2. Estimation (3 min)

Traffic

  • Active users: 300M monthly, ~50M daily
  • Add/remove/update operations: 50M DAU × 3 cart ops/day = 150M writes/day = ~1,750/sec average
  • Peak (Prime Day, 10x): ~17,500 writes/sec
  • Cart reads (page loads, mini-cart renders): 5× writes = ~8,750/sec average, 87,500/sec peak
  • Checkout transitions: 10M orders/day = ~115/sec average, 1,150/sec peak

Storage

  • Cart record: user_id + metadata = ~200 bytes
  • Cart item: item_id + seller_id + quantity + price_at_add + timestamp = ~150 bytes
  • Average cart: 5 items → 200 + (5 × 150) = ~950 bytes per cart
  • 500M carts × 950 bytes = ~475 GB total cart data
  • Including indexes, replicas, and overhead: ~2 TB provisioned storage

Inventory Checks

  • Every add-to-cart triggers an inventory check: 1,750/sec average
  • Every cart page load validates inventory for all items: 8,750/sec × 5 items = 43,750 inventory lookups/sec
  • Peak: ~440K inventory lookups/sec — must be served from cache, not direct DB

Guest Cart Volume

  • ~40% of carts are guest carts (no login)
  • 200M guest carts with average TTL of 30 days
  • Cart merge events on login: ~5M/day (guest converts to authenticated)

Key Insight

This is a hot-path, high-availability storage problem with an inventory coordination challenge. The cart is on the critical path of every purchase. The hard problems are: (1) keeping cart data durable without sacrificing read latency, (2) handling inventory races at checkout, and (3) merging guest and authenticated carts without losing items.


3. API Design (3 min)

Get Cart

GET /v1/cart
Headers: Authorization: Bearer {token}  (or X-Session-Id: {session_id} for guests)

Response 200: {
  "cart_id": "cart_a1b2c3",
  "user_id": "usr_456",               // null for guest carts
  "items": [
    {
      "item_id": "B08N5WRWNW",
      "seller_id": "seller_789",
      "title": "Sony WH-1000XM5 Headphones",
      "quantity": 1,
      "price": 34800,                  // current live price in cents
      "price_at_add": 32800,           // price when user added it
      "price_changed": true,           // flag for UI notification
      "currency": "USD",
      "in_stock": true,
      "available_quantity": 23,        // for "only X left" messaging
      "image_url": "...",
      "added_at": "2024-03-15T10:30:00Z"
    },
    ...
  ],
  "subtotal": 52700,                   // sum of (price × quantity) in cents
  "item_count": 3,
  "saved_for_later": [ ... ],          // separate list
  "shipping_estimates": [
    { "seller_id": "seller_789", "estimate": "Mar 18 - Mar 20", "cost": 0 },
    { "seller_id": "seller_222", "estimate": "Mar 19 - Mar 22", "cost": 599 }
  ],
  "updated_at": "2024-03-15T14:22:00Z"
}

Add Item to Cart

POST /v1/cart/items
Headers: Authorization: Bearer {token}
Body: {
  "item_id": "B08N5WRWNW",
  "seller_id": "seller_789",
  "quantity": 1,
  "idempotency_key": "add-B08N5WRWNW-1710500000"
}

Response 201: {
  "cart_id": "cart_a1b2c3",
  "item": { ... },                     // the added item with current price
  "item_count": 3,                     // updated total
  "subtotal": 52700
}

Update Item Quantity

PATCH /v1/cart/items/{item_id}
Body: { "quantity": 3 }

Response 200: { "item": { ... }, "subtotal": 157100 }

Remove Item from Cart

DELETE /v1/cart/items/{item_id}

Response 200: { "item_count": 2, "subtotal": 17900 }

Move to Saved for Later

POST /v1/cart/items/{item_id}/save-for-later

Response 200: { "saved_item": { ... }, "item_count": 2 }

Merge Guest Cart on Login

POST /v1/cart/merge
Body: { "guest_session_id": "sess_xyz789" }

Response 200: {
  "merged_items": 3,
  "conflicts": [
    {
      "item_id": "B08N5WRWNW",
      "guest_quantity": 2,
      "existing_quantity": 1,
      "resolved_quantity": 3            // summed
    }
  ]
}

Checkout (Cart → Order)

POST /v1/cart/checkout
Body: {
  "shipping_address_id": "addr_123",
  "payment_method_id": "pm_stripe_abc",
  "item_ids": ["B08N5WRWNW", "B09XYZ"]  // optional: partial checkout
}

Response 201: {
  "order_id": "ord_789",
  "status": "pending_payment",
  "reserved_until": "2024-03-15T14:35:00Z"  // 10-min reservation
}

Key Decisions

  • Prices in cents (integer arithmetic) — avoids floating-point rounding errors across currencies
  • Idempotency key on add — prevents duplicate items from retried requests (network flakiness, button double-clicks)
  • price_at_add vs live price — the API returns both so the UI can show “Price changed since you added this item”
  • Partial checkout — users can choose to check out a subset of cart items (common on Amazon)
  • Per-seller shipping estimates — multi-seller marketplace requires shipping calculation per seller

4. Data Model (3 min)

Carts (DynamoDB — primary durable store)

Table: carts
  Partition Key: cart_id      | varchar(50)
  user_id                     | varchar(50)       -- null for guest carts
  session_id                  | varchar(100)      -- for guest cart lookup
  status                      | enum('active', 'checked_out', 'expired', 'merged')
  created_at                  | timestamp
  updated_at                  | timestamp
  expires_at                  | timestamp          -- TTL for abandoned carts (30 days)

GSI-1: user_id → cart_id      (lookup cart by user)
GSI-2: session_id → cart_id   (lookup guest cart by session)

Cart Items (DynamoDB — same table, composite key)

Table: cart_items
  Partition Key: cart_id      | varchar(50)
  Sort Key: item_id           | varchar(50)
  seller_id                   | varchar(50)
  quantity                    | int
  price_at_add                | int               -- price in cents when added
  added_at                    | timestamp
  updated_at                  | timestamp

GSI: seller_id → (cart_id, item_id)   -- for seller-level queries

Cart Snapshots for Checkout (DynamoDB)

Table: cart_snapshots
  Partition Key: snapshot_id  | varchar(50)
  cart_id                     | varchar(50)
  items                       | JSON              -- frozen copy of items + prices
  subtotal                    | int
  created_at                  | timestamp
  expires_at                  | timestamp          -- 10-minute reservation TTL

Hot Cache Layer (Redis)

Key: cart:{user_id}
Value: Full cart JSON (items, quantities, prices)
TTL: 24 hours (refreshed on every read/write)

Key: cart:guest:{session_id}
Value: Full cart JSON
TTL: 30 days

Key: cart:lock:{cart_id}
Value: lock_token
TTL: 5 seconds (distributed lock for checkout)

Why DynamoDB + Redis Hybrid

  • DynamoDB for durability: Single-digit millisecond reads at any scale. Auto-scales for Prime Day. TTL-based automatic cleanup of expired carts (no cron jobs). Point-in-time recovery for disaster scenarios.
  • Redis for hot path: Sub-millisecond reads. Cart page renders hit Redis first. Write-through: every cart mutation writes to both Redis and DynamoDB. If Redis misses, read from DynamoDB and populate cache.
  • Why not pure Redis: Redis is not durable enough for cart data. Even with AOF persistence, a Redis node failure can lose the last few seconds of writes. Losing a cart = losing a sale.
  • Why not pure DynamoDB: DynamoDB p99 is 5-10ms. For a cart page that every user sees on every visit, we want sub-millisecond. Redis gives us that hot-path performance.
  • Why not PostgreSQL: Cart access patterns are key-value (get cart by user_id, get items by cart_id). No complex joins or transactions needed for cart CRUD. DynamoDB’s partition-key access is perfect. PostgreSQL would require sharding at this scale (500M carts) and offers no advantage.

5. High-Level Design (12 min)

Architecture

User (Browser / Mobile App)
  → CloudFront CDN (static assets, product images)
  → API Gateway (auth, rate limiting, routing)
      → Cart Service (core CRUD)
          → Redis Cluster (hot cache, sub-ms reads)
          → DynamoDB (durable storage, auto-scaling)
      → Inventory Service (stock checks)
          → Inventory Cache (Redis)
          → Inventory DB (DynamoDB / Aurora)
      → Pricing Service (live prices)
          → Price Cache (Redis)
      → Checkout Service (cart → order transition)
          → Order Service
          → Payment Service
          → Inventory Reservation Service
      → Notification Service (price alerts, abandoned cart emails)

Add-to-Cart Flow

1. User clicks "Add to Cart" for item B08N5WRWNW

2. API Gateway → Cart Service:
   a. Validate idempotency_key
      → Redis: GET idempotency:{key}
      → If exists: return cached response (duplicate request)

   b. Check inventory (fast path):
      → Inventory Cache (Redis): GET stock:{item_id}:{seller_id}
      → If available_quantity < requested_quantity: return "Out of Stock"
      → Note: This is a soft check. Real reservation happens at checkout.

   c. Get current price:
      → Price Cache (Redis): GET price:{item_id}:{seller_id}
      → Store as price_at_add on the cart item

   d. Write to cart (write-through):
      → Redis: HSET cart:{user_id} {item_id} {item_json}
      → DynamoDB: PutItem(cart_items, {cart_id, item_id, quantity, price_at_add, ...})
      → Both writes happen in parallel. If DynamoDB write fails, queue for retry.
      → Redis write is synchronous (fast). DynamoDB write is async with retry.

   e. Set idempotency key:
      → Redis: SET idempotency:{key} {response} EX 3600

   f. Return response with updated cart summary

Cart Read Flow

1. User opens cart page or mini-cart dropdown

2. Cart Service:
   a. Read cart from Redis (fast path):
      → HGETALL cart:{user_id}
      → Cache hit (~98% of the time): proceed to step (c)

   b. Cache miss — read from DynamoDB:
      → Query(cart_items, PK = cart_id)
      → Populate Redis cache
      → HSET cart:{user_id} {all items}

   c. Enrich with live data (parallel fan-out):
      → For each item in cart (batched):
        → Inventory Service: batch GET stock:{item_id_1}, stock:{item_id_2}, ...
        → Pricing Service: batch GET price:{item_id_1}, price:{item_id_2}, ...

   d. Build response:
      → For each item:
        → Set in_stock = (available_quantity > 0)
        → Set price = live price from Pricing Service
        → Set price_changed = (live price != price_at_add)
        → Set available_quantity for "Only X left" badge

   e. Compute shipping estimates (parallel, per seller):
      → Group items by seller_id
      → For each seller: call Shipping Service with items + destination
      → Return estimated delivery window and cost per seller

   f. Return full cart response

Guest-to-Authenticated Cart Merge Flow

1. Guest user has session_id = "sess_xyz" with 3 items in cart
2. User logs in with user_id = "usr_456" who has existing cart with 2 items

3. POST /v1/cart/merge { guest_session_id: "sess_xyz" }

4. Cart Service:
   a. Load guest cart:
      → Redis: HGETALL cart:guest:sess_xyz
      → Items: [A (qty 2), B (qty 1), C (qty 1)]

   b. Load authenticated cart:
      → Redis: HGETALL cart:usr_456
      → Items: [A (qty 1), D (qty 1)]

   c. Merge strategy (item-level):
      → Item A exists in both: SUM quantities → qty = 3
        (Alternatively: MAX or prompt user. Amazon uses SUM.)
      → Item B: only in guest → add to authenticated cart
      → Item C: only in guest → add to authenticated cart
      → Item D: only in authenticated → keep as-is

   d. Validate merged quantities:
      → Check each merged item quantity against inventory max
      → If A has max_per_order = 2: cap at 2, notify user

   e. Write merged cart:
      → Redis: HSET cart:usr_456 { A:3, B:1, C:1, D:1 }
      → DynamoDB: BatchWriteItem for all merged items

   f. Mark guest cart as merged:
      → DynamoDB: UpdateItem(carts, sess_xyz, status = 'merged')
      → Redis: DEL cart:guest:sess_xyz

   g. Return merge result with any conflicts/adjustments

Checkout Transition Flow

1. User clicks "Proceed to Checkout" with 3 items

2. Checkout Service:
   a. Acquire distributed lock:
      → Redis: SET cart:lock:{cart_id} {token} NX EX 5
      → Prevents concurrent checkout attempts for same cart

   b. Create cart snapshot (frozen point-in-time):
      → Read cart items with LIVE prices from Pricing Service
      → DynamoDB: PutItem(cart_snapshots, {snapshot_id, items, prices, totals})
      → This is the price the user will pay (no further changes)

   c. Reserve inventory (atomic, per item):
      → For each item in cart:
        → Inventory Service: POST /reserve
          {item_id, seller_id, quantity, reservation_id, ttl: 600}
        → Uses conditional write:
          UpdateItem SET reserved = reserved + qty
          WHERE (stock - reserved) >= qty
        → If any item fails reservation: roll back all previous reservations

   d. Create order:
      → Order Service: POST /orders
        {snapshot_id, items, shipping, payment_method}
      → Order status = "pending_payment"

   e. Remove checked-out items from cart:
      → Cart Service: remove items (or mark cart as checked_out if full checkout)

   f. Release lock:
      → Redis: DEL cart:lock:{cart_id} (with token validation)

   g. Return order_id + payment URL

3. If payment succeeds (within 10 min):
   → Inventory: convert reservation to committed sale
   → Order: status = "confirmed"

4. If payment fails or times out:
   → Inventory: release reservation
   → Cart: restore items (if desired)
   → Order: status = "cancelled"

Components

  1. Cart Service: Core CRUD for cart items. Write-through to Redis + DynamoDB. Handles guest/authenticated carts, merging, and save-for-later. Stateless — any instance can serve any request.
  2. Inventory Service: Maintains real-time stock counts. Provides soft-check (is item in stock?) and hard-reservation (decrement stock atomically). Uses DynamoDB conditional writes for atomic operations.
  3. Pricing Service: Returns live prices for items. Prices can change at any time (seller updates, dynamic pricing, promotions). Cart always displays live prices, not stale cached ones.
  4. Checkout Service: Orchestrates the cart-to-order transition. Acquires locks, creates snapshots, reserves inventory, and hands off to Order Service. Implements saga pattern for rollback on failure.
  5. Shipping Estimation Service: Calculates delivery estimates per seller based on seller warehouse locations, buyer address, and shipping speed. Called on cart page render.
  6. Abandoned Cart Worker: Processes carts not updated in 24 hours. Sends reminder emails (1 hour, 24 hours, 7 days). DynamoDB TTL auto-deletes carts after 30 days of inactivity.
  7. Cart Merge Service: Handles the guest-to-authenticated transition. Resolves conflicts (duplicate items, quantity caps). Triggered on login events via an event bus.

6. Deep Dives (15 min)

Deep Dive 1: Inventory Race Conditions — Last Item in Stock

The problem: 1,000 users simultaneously add the last unit of a viral product to their cart. At checkout, only 1 can actually buy it. How do we handle this without a terrible user experience?

Approach: Two-tier inventory model (soft check vs hard reservation)

Tier 1: Soft Check (Add-to-Cart time)
═══════════════════════════════════════
- When user clicks "Add to Cart," we check a cached inventory count in Redis
- This is a non-blocking, non-decrementing check
- If stock > 0: allow add-to-cart (item appears in cart)
- If stock = 0: show "Out of Stock" immediately
- We do NOT reserve inventory at add-to-cart time

Why not reserve at add-to-cart?
- Users add items and abandon carts 70% of the time
- Reserving at add-to-cart would lock out real buyers
- 1000 users adding last item → item unavailable for hours while carts expire
- Amazon explicitly chose this model: cart does not guarantee availability

Tier 2: Hard Reservation (Checkout time)
═══════════════════════════════════════
- When user clicks "Proceed to Checkout," we atomically reserve inventory
- DynamoDB conditional write:
    UpdateExpression: SET reserved = reserved + :qty
    ConditionExpression: (stock - reserved) >= :qty
- Only succeeds if sufficient unreserved stock exists
- First user to reach checkout wins
- Losers get: "Sorry, this item is no longer available"

The race at checkout:
  User A: UpdateItem SET reserved = reserved + 1 WHERE (stock - reserved) >= 1
  User B: UpdateItem SET reserved = reserved + 1 WHERE (stock - reserved) >= 1

  DynamoDB uses optimistic locking internally.
  One write succeeds, the other gets ConditionalCheckFailedException.
  No distributed locks needed — the database is the coordination point.

Handling the user experience for losers:

When checkout fails for item X (out of stock):
  1. Remove item X from the checkout, proceed with remaining items
  2. Move item X to "Saved for Later" automatically
  3. Offer to notify user when item X is back in stock
  4. Show alternative/similar items (recommendation engine)

This is significantly better than blocking add-to-cart.
User can still browse, build their cart, and only learns about
stock-outs at the commitment point (checkout).

Inventory count accuracy in the cart page:

Problem: User has item in cart for 3 days. Stock might be 0 by now.

Solution: Every cart page render re-checks inventory (batched):
  → Cart has items [A, B, C]
  → Batch call: Inventory Service GET /stock?items=A,B,C
  → Response: { A: 5, B: 0, C: 2 }
  → Item B shown as: "Currently unavailable" with option to remove or save-for-later
  → Item C shown as: "Only 2 left in stock — order soon"

Cache TTL for inventory counts: 10 seconds
  → Acceptable staleness for cart display
  → Checkout always reads from source-of-truth (not cache)

Deep Dive 2: Price Consistency — What Happens When Prices Change

The problem: User adds a $328 headphone to cart on Monday. On Wednesday, the price increases to $348. On Thursday, a flash sale drops it to $279. What price does the user see? What price do they pay?

Amazon’s model: Live price display, snapshot at checkout

Phase 1: In the Cart (live price, always current)
══════════════════════════════════════════════════
- Cart page always shows the CURRENT live price, not price-at-add
- If price changed since add-to-cart, show notification:
  "Price increased by $20 since you added this item"
  "Price dropped by $49 since you added this item — you save $49!"

- Implementation:
  Cart item stores: price_at_add = 32800 (recorded at add time)
  Cart read enriches with: current_price = Pricing Service lookup
  API returns both, plus: price_changed = (current_price != price_at_add)
  UI renders the delta with appropriate messaging

Why live price, not price-at-add?
  - Sellers change prices constantly (repricing algorithms run every 15 min)
  - Honoring stale prices for days would be exploitable
  - Amazon's cart is a "wish list with intent" — not a price guarantee
  - Users expect to see accurate prices when they make the buy decision

Phase 2: At Checkout (price snapshot, locked)
══════════════════════════════════════════════════
- When user clicks "Proceed to Checkout":
  1. Fetch live price for ALL items in cart
  2. Create an immutable cart snapshot in DynamoDB:
     {
       snapshot_id: "snap_abc",
       items: [
         { item_id: "B08N5...", price: 27900, quantity: 1 },
         ...
       ],
       subtotal: 45800,
       created_at: "2024-03-15T14:30:00Z",
       expires_at: "2024-03-15T14:40:00Z"   // 10-minute lock
     }
  3. This snapshot IS the price the user pays
  4. Even if price changes during the 10-min checkout window, the snapshot price holds
  5. Snapshot expires after 10 min — user must re-initiate checkout (new snapshot)

Why snapshot at checkout instead of payment?
  - Checkout takes ~2-5 minutes (enter address, select shipping, confirm)
  - Prices changing DURING checkout would be infuriating
  - 10-minute snapshot window is a reasonable compromise
  - If user takes too long: "Your prices may have changed. Please review."

Phase 3: Post-order price changes
══════════════════════════════════════════════════
- After order is placed, price is locked permanently
- If price drops within 7 days (Amazon's post-order price guarantee for some categories):
  → Automatic partial refund of the difference
  → Implemented by a price monitoring worker that compares order prices to current prices

Price consistency at scale:

Challenge: 500M carts × 5 items = 2.5B item-price pairs to keep fresh
  → Cannot push price updates to all carts in real-time

Solution: Pull model (lazy evaluation)
  → Prices are NOT stored in the cart (except price_at_add for comparison)
  → Every cart read fetches live prices from Pricing Service
  → Pricing Service serves from Redis cache (sub-ms, 500K lookups/sec)
  → Cache TTL: 60 seconds (prices refresh every minute)
  → Price change events update the cache proactively via Kafka

This means: cart storage is decoupled from price storage.
Cart only stores: what items the user wants and when they added them.
Price is always fetched live at render time.

Deep Dive 3: Cart Durability and High Availability

The problem: Cart loss = lost revenue. If a user’s cart disappears, they rarely re-add all items. Amazon estimated that cart unavailability costs ~$1M/minute during peak. How do we ensure carts survive every failure mode?

Multi-layer durability strategy:

Layer 1: Redis Cluster (hot path — speed)
══════════════════════════════════════════
- 3 Redis clusters across 3 availability zones
- Each cluster: 50 shards, 1 primary + 2 replicas per shard
- Hash-based partitioning: CRC16(user_id) mod 50 → shard
- Replication: async with < 1ms replication lag
- Failover: Redis Sentinel promotes replica in < 3 seconds

Failure modes:
  → Single node failure: Sentinel promotes replica. < 3s downtime.
     Cart reads fall back to DynamoDB during failover. No data loss.
  → Full AZ failure: Replicas in other AZs take over. < 10s.
  → Full Redis cluster failure: 100% traffic falls back to DynamoDB.
     Latency degrades from <1ms to ~5ms. Still within SLA.

Layer 2: DynamoDB (durable store — persistence)
══════════════════════════════════════════
- Global table replicated across 3 regions (us-east-1, us-west-2, eu-west-1)
- On-demand capacity mode: auto-scales from 1K to 1M WCU without pre-provisioning
- Point-in-time recovery enabled: can restore to any second in the last 35 days
- DynamoDB guarantees 99.999% availability for global tables

Write path: write-through
  → Cart write → Redis (sync, fast) + DynamoDB (async, durable)
  → If Redis write succeeds but DynamoDB write fails:
    → Queue the write in an SQS dead-letter queue
    → Retry with exponential backoff (max 3 attempts)
    → If all retries fail: alert on-call, Redis has the data

Read path: cache-aside with fallback
  → Read Redis → hit: return (98% of reads)
  → Read Redis → miss: read DynamoDB → populate Redis → return

Layer 3: Client-side backup (belt and suspenders)
══════════════════════════════════════════
- Mobile app / browser caches cart state in local storage
- If server returns error, show last-known cart from local cache
- On next successful request, reconcile local vs server state
- This gives perceived availability even during server-side outages

Session affinity vs stateless:

Cart Service is STATELESS. No session affinity needed.
  → Any Cart Service instance can serve any user's request
  → Cart state lives in Redis/DynamoDB, not in the application server
  → This enables seamless horizontal scaling and rolling deployments
  → During Prime Day: auto-scale from 100 to 1000+ instances in minutes

Why NOT session affinity:
  → Sticky sessions create hot spots (one instance handles all requests for heavy users)
  → Instance failure loses all "sticky" users — they get errors until re-routed
  → Rolling deploys become complex (need to drain sessions before terminating)
  → With Redis as the shared cache, there's no benefit to affinity

Abandoned cart lifecycle:

Timeline:
  T+0:     User adds item to cart
  T+1h:    Abandoned Cart Worker detects inactivity
             → Send push notification: "You left something in your cart!"
  T+24h:   Send email with cart contents and "Complete your purchase" CTA
  T+7d:    Send final email with potential discount/coupon
  T+30d:   DynamoDB TTL auto-deletes the cart record
             → Redis key already expired (24h TTL, not refreshed)

Implementation:
  → DynamoDB Streams triggers Lambda on cart item writes
  → Lambda updates a "last_activity" timestamp
  → Abandoned Cart Worker queries:
      GSI: status='active' AND updated_at < (now - 1 hour)
  → Sends events to Notification Service via SQS
  → DynamoDB TTL field (expires_at) auto-deletes after 30 days
    → No cron job needed for cleanup

7. Extensions (2 min)

  • Saved for Later / Wish List integration: “Save for Later” moves an item out of the active cart into a persistent list. This list can sync with the user’s wish list. Items in Saved for Later can be monitored for price drops and back-in-stock events, with push notifications to re-engage the user. Implementation: separate DynamoDB table with the same schema as cart_items, plus a “source” field (saved_from_cart vs wish_list).
  • Multi-seller cart with split shipments: Items from different sellers ship from different warehouses. The cart must group items by seller, estimate shipping per group, and display separate delivery windows. At checkout, a single order is split into multiple fulfillment sub-orders (one per seller). Each sub-order independently reserves inventory, ships, and tracks. The Checkout Service orchestrates this split using the saga pattern, with per-seller compensation on failure.
  • Cart-level promotions and coupons: Users apply promo codes at the cart level. The Promotions Service evaluates eligibility: minimum spend, item category restrictions, single-use codes, stackability rules. Cart totals are re-computed on every change. Coupons expire independently of the cart. During Prime Day, lightning deal prices override regular prices for eligible items in the cart, with a countdown timer and quantity limit.
  • Cross-device real-time sync: When a user adds an item on their phone, the desktop browser should reflect it within 2 seconds. Implementation: Cart writes publish events to a WebSocket notification channel (per user). The client subscribes to ws://cart-updates/{user_id}. On receiving an event, the client re-fetches the cart. For offline/background tabs, the next cart page load fetches the latest state.
  • Cart analytics and ML-driven recommendations: Every cart action (add, remove, quantity change, save-for-later) is streamed to a Kinesis pipeline. ML models analyze: (1) which items are frequently added together (bundle recommendations), (2) which items are frequently removed (price sensitivity signal), (3) cart abandonment patterns by time-of-day, device, and item category. The “Frequently bought together” and “Customers who bought this also bought” widgets on the cart page are powered by these signals.