45-minute system design interview format - Fullstack Engineer Position
"Today I'll design an e-commerce platform like Amazon, focusing on end-to-end flows that span frontend and backend. The key technical challenges are building a responsive shopping experience with real-time inventory feedback, implementing a robust checkout flow that prevents overselling while maintaining excellent UX, and creating a search experience with faceted filtering that stays fast at scale. I'll walk through how these components integrate across the stack."
- Product Discovery: Search with faceted filtering, category browsing
- Shopping Cart: Add/remove items with real-time inventory feedback
- Checkout Flow: Multi-step process with payment integration
- Order Tracking: View order history and status updates
- Recommendations: "Also bought" suggestions on product pages
- Availability: 99.99% for browsing and cart operations
- Consistency: Strong consistency for inventory (no overselling)
- Latency: < 100ms for API responses, < 50ms for UI updates
- Scale: 100M products, 1M orders/day, 500K concurrent users
| Operation | Volume | E2E Latency Target |
|---|---|---|
| Product search | 100K QPS | < 300ms total |
| Add to cart | 10K QPS | < 200ms total |
| Checkout | 1K QPS | < 2s total |
| Page load | 500K concurrent | < 1s TTI |
┌─────────────────────────────────────────────────────────────────────────┐
│ FRONTEND LAYER │
│ React + TanStack Router + Zustand + TanStack Query │
├─────────────────────────────────────────────────────────────────────────┤
│ Product Search │ Product Detail │ Shopping Cart │ Checkout Flow │
│ - Faceted UI │ - Image gallery │ - Cart sidebar │ - Multi-step │
│ - Virtualized │ - Recommendations - Quantity │ - Payment │
│ - Infinite │ - Reviews │ - Inventory │ - Confirmation │
└────────┬─────────┴────────┬─────────┴────────┬────────┴────────┬────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ API GATEWAY │
│ Rate Limiting + Auth + CORS │
└────────┬─────────────────┬─────────────────┬────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Catalog Service │ │ Cart Service │ │ Order Service │
│ - Search API │ │ - Cart CRUD │ │ - Checkout │
│ - Product API │ │ - Reservations │ │ - Idempotency │
│ - Recommendations│ │ - Inventory │ │ - Order history │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ DATA LAYER │
├──────────────┬──────────────┬──────────────┬────────────────────────────┤
│ PostgreSQL │ Elasticsearch│ Valkey │ Kafka │
│ - Products │ - Search │ - Sessions │ - Order events │
│ - Orders │ - Facets │ - Cart │ - Inventory updates │
│ - Inventory │ │ - Cache │ - Recommendations │
└──────────────┴──────────────┴──────────────┴────────────────────────────┘
Separation of Concerns: Each service handles one domain, enabling independent scaling and deployment.
Optimistic UI: Frontend assumes success and rolls back on failure, providing instant feedback.
Event-Driven Updates: Kafka enables async processing (recommendations, notifications) without blocking user flows.
This is the most critical user journey, requiring tight frontend-backend coordination.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │ │ Cart API │ │ PostgreSQL │ │ Valkey │
│ (React) │ │ (Express) │ │ (Inventory) │ │ (Cache) │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
│ 1. Click "Add" │ │ │
│ ──────────────▶ │ │ │
│ 2. Optimistic │ │ │
│ UI Update │ │ │
│ ◀────────────── │ │ │
│ │ 3. BEGIN TRANS │ │
│ │ ──────────────▶ │ │
│ │ 4. SELECT... │ │
│ │ FOR UPDATE │ │
│ │ ──────────────▶ │ │
│ │ 5. Check avail │ │
│ │ ◀────────────── │ │
│ │ 6. UPDATE │ │
│ │ reserved += │ │
│ │ ──────────────▶ │ │
│ │ 7. INSERT cart │ │
│ │ ──────────────▶ │ │
│ │ 8. COMMIT │ │
│ │ ◀────────────── │ │
│ │ │ 9. Invalidate │
│ │ │ cart cache │
│ │ ─────────────────────────────────▶ │
│ 10. Confirm │ │ │
│ ◀────────────── │ │ │
CartStore (Zustand with persist):
- State:
items[],isLoading,error - Methods:
addItem,removeItem,updateQuantity,clearCart,getTotal
addItem() Flow:
- Store previous items for rollback
- Optimistic update: add item immediately with 30-min reservation
- POST to
/api/cart/items - On success: update
reservedUntilfrom server response - On failure: rollback to previous items, set error
Rollback Pattern:
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ previousItems │ │ API Call │ │ Result │
│ stored │──▶ │ attempt │──▶ │ │
└───────────────┘ └───────────────┘ └───────────────┘
│ │
│ ▼
│ ┌─────────────┐
│ │ Success │──▶ Update reservedUntil
│ └─────────────┘
│ │
│ ┌─────────────┐
└─────────────▶│ Failure │──▶ Restore previousItems
└─────────────┘
POST /items Endpoint:
- Get connection from pool, BEGIN transaction
- Lock inventory row with
SELECT ... FOR UPDATE - Check
available = quantity - reserved - If insufficient: throw
InsufficientInventoryError - UPDATE
reserved += quantity - INSERT/UPSERT cart item with
reserved_until - COMMIT transaction
- Invalidate cart cache in Redis
- Return cart item
Error Response:
- 409 Conflict:
INSUFFICIENT_INVENTORYwith available count - Frontend can show "Only X units available"
Frontend handleAddToCart():
- On
InsufficientInventoryError: toast "Only X available", invalidate product query - On
ReservationExpiredError: toast "Expired, try again" - On generic error: toast "Failed, try again"
User types "wireless headphones"
│
▼
┌──────────────────┐
│ SearchInput.tsx │──▶ debounce(300ms) ──▶ URL update
│ - Controlled │ /search?q=wireless+headphones
│ - Debounced │
└──────────────────┘
│
▼
┌──────────────────┐
│ useSearchQuery │──▶ GET /api/search?q=...
│ - Cache 5min │ TanStack Query
│ - Stale-while- │
│ revalidate │
└──────────────────┘
│
▼
┌──────────────────┐
│ Catalog Service │──▶ Elasticsearch
│ - ES query build │ products index
│ - Aggregations │
│ - Circuit breaker│
└──────────────────┘
│
▼
┌──────────────────┐
│ SearchResults │◀── Response:
│ - Virtualized │ - products[]
│ - Facets sidebar │ - facets{}
│ - Infinite scroll│ - totalCount
└──────────────────┘
SearchPage component:
- Uses
useSearchParamsfor URL state - Extracts:
query,category,priceMin,priceMax,brands[] - Uses
useInfiniteQueryfor paginated results getNextPageParam: returns page number ifhasMorestaleTime: 5 minutes
Virtualization with TanStack Virtual:
count: products + 1 (for loading indicator)estimateSize: 280px per rowoverscan: 5 items
Infinite Scroll Trigger:
- Watch last virtual item index
- If near end and
hasNextPage: callfetchNextPage()
Facets Sidebar:
- Receives
facetsfrom first page response selectedstate from URL paramsonChangeupdates URL params
GET / Handler:
- Try Elasticsearch with circuit breaker
- Build ES query with filters
- Extract products and facets from response
- Log search for analytics
- If circuit open: fallback to PostgreSQL FTS
Elasticsearch Query Structure:
function_scorefor relevance boostingbool.must: fuzzy match on titlebool.filter: category, price range, brand terms- Boost factors: in_stock (2x), rating (sqrt, 1.2x)
Aggregations:
categories: top 20 termsbrands: top 20 termsprice_ranges: Under $25, $25-50, $50-100, Over $100avg_rating: average value
Fallback Strategy:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Elasticsearch │──X──│ Circuit Breaker │──▶ │ PostgreSQL FTS │
│ Primary │ │ OPEN │ │ Fallback │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Step 1 │ ─▶ │ Step 2 │ ─▶ │ Step 3 │ ─▶ │ Step 4 │
│ Shipping │ │ Payment │ │ Review │ │ Confirmation│
│ │ │ │ │ │ │ │
│ - Address │ │ - Card form │ │ - Summary │ │ - Order ID │
│ - Validation│ │ - Stripe │ │ - Edit │ │ - Email │
│ - Save │ │ Elements │ │ - Place │ │ - Next steps│
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
CheckoutFlow component:
- Uses XState machine for flow control
- Generates
idempotencyKeyon mount - Renders step indicator with completion status
- Conditionally renders step components
Step Indicator:
- Shows checkmark for completed steps
- Highlights current step
- Numbered circles for pending steps
State Machine Definition:
┌────────────┐ SUBMIT_SHIPPING ┌────────────┐
│ shipping │ ─────────────────────▶ │ payment │
└────────────┘ └────────────┘
│
BACK │ SUBMIT_PAYMENT
◀───────────────────────┘
│
▼
┌────────────┐
│ review │
│ ┌──────┐ │
│ │ idle │ │
│ └──┬───┘ │
│ │ PLACE_ORDER
│ ▼ │
│ ┌────────┐ │
│ │placing │ │
│ └──┬─────┘ │
└────┼───────┘
onDone ─────────┘└────────── onError
│ │
▼ ▼
┌─────────────┐ ┌───────────┐
│confirmation │ │ error │
│ (final) │ │ (RETRY) │
└─────────────┘ └───────────┘
Context:
shippingAddress,paymentMethod,orderId,error,completedSteps[]
POST / Handler (11-step process):
- Idempotency Check: Query existing order by key, return cached response
- BEGIN Transaction: Get pooled connection
- Get Cart with Lock:
SELECT ... FOR UPDATE OF inventory - Verify Availability: Check all items still available
- Calculate Totals: subtotal + tax (8%) + shipping ($5.99 or free over $50)
- Process Payment: Stripe with
payment-{idempotencyKey} - Create Order: INSERT with status
confirmed - Copy Items: INSERT order_items FROM cart_items
- Commit Inventory: UPDATE quantity - X, reserved - X
- Clear Cart: DELETE cart_items
- COMMIT + Events: Cache response, emit to Kafka
Error Handling:
- 402 Payment Required:
PAYMENT_FAILED - Rollback on any error
- Audit log for failed checkouts
Kafka Consumer (inventory-sync group):
- On
INVENTORY_UPDATED: Update ES document, invalidate Redis cache
Frontend WebSocket Hook (useInventoryUpdates):
- Subscribe to product IDs
- On message: update query cache with new availability
- If cart item became unavailable: show warning toast
syncProductToElasticsearch() Background Job:
- Query product with inventory, category, seller joins
- If deleted: remove from index
- Otherwise: index with all searchable fields
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ PostgreSQL │──▶ │ Background │──▶ │Elasticsearch│
│ (source) │ │ Job │ │ (index) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ ┌─────────────┐ │
└────────▶│ Valkey │◀───────────┘
│ (cache) │
└─────────────┘
| Aspect | Chosen Approach | Alternative | Rationale |
|---|---|---|---|
| Cart Operations | Optimistic update | Wait for server | User perceives instant response |
| Rollback | Client-side state restoration | Server push | Simpler, works offline |
| Trade-off | Brief inconsistency on failure | Slower perceived performance | UX wins for common success case |
| Aspect | Chosen Approach | Alternative | Rationale |
|---|---|---|---|
| Flow Control | XState machine | useState flags | Clear states, impossible transitions prevented |
| Persistence | Context in machine | localStorage | Survives refresh, tracks progress |
| Trade-off | Learning curve | Simpler but error-prone | Correctness for critical flow |
| Aspect | Chosen Approach | Alternative | Rationale |
|---|---|---|---|
| Primary Search | Elasticsearch | PostgreSQL FTS | Performance, faceted filtering |
| Fallback | PostgreSQL FTS on circuit open | Return error | Degraded but available |
| Trade-off | Maintain two search impls | Single point of failure | Availability over consistency |
| Aspect | Chosen Approach | Alternative | Rationale |
|---|---|---|---|
| Cart Inventory | Reserve on add | Decrement on add | Prevents false out-of-stock |
| Expiration | 30-minute TTL | No expiration | Balance UX vs. availability |
| Trade-off | Background cleanup job | Simpler but inventory locks | Fairness to all users |
| Decision | Pros | Cons |
|---|---|---|
| Optimistic UI | Instant feedback, better UX | Rollback complexity, brief inconsistency |
| Reserved inventory | Accurate availability, no overselling | Cleanup job needed, complexity |
| State machine checkout | Predictable flow, easy debugging | Learning curve, more code |
| ES + PG fallback | High availability, fast search | Two systems to maintain |
| Idempotency keys | Exactly-once orders, safe retries | Key storage overhead, 24h TTL management |
| WebSocket inventory | Real-time updates, better UX | Connection management, scaling |
- Progressive Web App: Offline cart access, push notifications for order updates
- Server-Sent Events: Alternative to WebSocket for inventory updates, simpler scaling
- GraphQL Federation: Unified API across services with client-driven queries
- Edge Caching: CDN caching for product pages with stale-while-revalidate
- A/B Testing Infrastructure: Feature flags for checkout flow experiments
- Micro-Frontends: Independent deployment of search, cart, checkout modules