Design an E-Commerce Platform
Design an e-commerce platform with product catalog, shopping cart, inventory management, checkout, and order processing.
Problem Statement
Design an e-commerce platform like Amazon that supports a product catalog with search, a shopping cart, real-time inventory management, a checkout flow with payment processing, and order tracking. Must handle flash sales where thousands of users attempt to buy limited-stock items simultaneously.
Requirements
Functional
- Browse and search a product catalog (100M+ SKUs) with filters (price, category, rating, availability)
- Add/remove items to/from a persistent shopping cart
- Checkout: reserve inventory, process payment, create order, send confirmation
- Order tracking with status updates (confirmed, shipped, delivered)
Non-Functional
- Latency: Product page loads in <300ms, checkout completes in <5 seconds
- Consistency: Inventory must be strongly consistent -- never oversell
- Scale: 300M active customers, 10K orders/second peak (Black Friday), 100M SKUs
- Availability: 99.99% for browsing; 99.95% for checkout
Core Architecture
-
Product Catalog Service -- Manages product metadata (title, description, images, price, category). Stored in PostgreSQL, indexed in Elasticsearch for full-text search and faceted filtering. Product pages are cached in Redis with a 5-minute TTL. Product images served via CDN.
-
Inventory Service -- Tracks available quantity per SKU per warehouse. Uses PostgreSQL with row-level locking:
UPDATE inventory SET qty = qty - 1 WHERE sku_id = ? AND qty > 0. Returns the number of affected rows -- 0 means out of stock. For flash sales, pre-loads hot SKU counts into Redis with Lua script for atomic decrement.
-
Cart Service -- Stores cart state in Redis (hash per user_id) for logged-in users and in local storage for guests. Cart merges on login. Prices are re-validated at checkout time (not stored in cart) to prevent stale price exploits.
-
Order Service -- Orchestrates checkout as a saga: (1) Reserve inventory, (2) Charge payment via Stripe/payment gateway, (3) Create order record, (4) Send confirmation email via async queue. If payment fails, release inventory reservation. If order creation fails, refund payment. Each step is idempotent with a unique order_id.
-
Search Service -- Elasticsearch cluster indexing product metadata with custom ranking: relevance score * (1 + log(sales_count)) * availability_boost. Supports autocomplete, spell correction, and category-scoped search.
Database Choice
PostgreSQL for products, orders, and inventory -- ACID transactions critical for inventory accuracy and order integrity. Redis for cart storage and product page cache. Elasticsearch for product search with faceted filtering. Kafka for order events (order created, payment confirmed, shipped) consumed by notification, analytics, and fulfillment services.
Key API Endpoints
GET /api/v1/products/search?q=laptop&category=electronics&min_price=500&sort=relevance
-> Returns: \{ products: [...], facets: \{ brands: [...], price_ranges: [...] \}, total: 4521 \}
POST /api/v1/checkout
-> Body: \{ cart_id: "C-456", shipping_address_id: "A-1", payment_method_id: "PM-7" \}
-> Returns: \{ order_id: "ORD-789", status: "CONFIRMED", estimated_delivery: "2024-01-15" \}
GET /api/v1/orders/\{order_id\}
-> Returns: \{ order_id: "ORD-789", status: "SHIPPED", tracking_number: "...", items: [...] \}
Scaling Insight
For flash sales, the standard database-level inventory check becomes a bottleneck. Solution: pre-load the limited inventory count into Redis and use a Lua script for atomic decrement: if redis.call('get', key) > 0 then redis.call('decr', key) return 1 else return 0 end. This handles 100K concurrent purchase attempts for a single SKU without touching the database. The actual database reservation happens asynchronously after the Redis decrement succeeds, within a 5-minute reservation window.
Key Tradeoffs
| Decision | Option A | Option B | Chosen |
|---|---|---|---|
| Inventory check | Database row lock per request | Redis atomic decrement for hot items | Redis for flash sales, database for normal flow -- best of both worlds |
| Checkout | Synchronous (all-or-nothing) | Saga with compensating transactions | Saga -- more resilient to partial failures, each step independently recoverable |
| Cart storage | Database (durable) | Redis (fast) | Redis with async database backup -- speed for the 99%, durability for the 1% who lose session |
Practical Implementation for .NET Developers
In a .NET application, you would typically implement this pattern using the following approach:
ASP.NET Core setup: Create a service class that encapsulates the logic, register it with dependency injection, and inject it into your controllers or minimal API endpoints. The built-in DI container handles lifecycle management.
Entity Framework Core: For database interactions, EF Core provides the ORM layer. Use migrations for schema management and raw SQL for performance-critical queries. Consider Dapper for read-heavy paths where EF Core's overhead matters.
Azure integration: If deploying to Azure, leverage managed services — Azure Cache for Redis, Azure SQL, Azure Service Bus, Azure Cosmos DB. These eliminate operational overhead and provide built-in monitoring through Application Insights.
Testing: Use xUnit with Testcontainers for integration tests that spin up real databases in Docker. Mock external dependencies with NSubstitute. The WebApplicationFactory class lets you test your entire HTTP pipeline in-process.
Monitoring: Add Application Insights telemetry to track request latency, dependency calls, and custom metrics. Use structured logging with Serilog to make production debugging possible:
Log.Information("Processing order {OrderId} for {CustomerId}", orderId, customerId);
This gives you searchable, structured logs in Azure Monitor or Seq.
Deep-Dive: Clarifying Questions for E-Commerce Platform
- What is the transaction volume? Amazon handles 66,000 orders per hour on a normal day. During Prime Day, this spikes to 300,000+ orders per hour. Black Friday sees similar surges.
- How do we handle flash sales? When 100,000 users try to buy 1,000 units of a hot item simultaneously, how do we prevent overselling without making the system unusably slow?
- How does inventory management work? Real-time inventory across multiple warehouses with reservation-style locking. The worst customer experience is "Sorry, that item sold out after you added it to cart."
- Do we need a recommendation engine? "Customers who bought this also bought..." drives 35% of Amazon's revenue. Product recommendations are high-impact.
- How do we handle the shopping cart? Carts need to persist across sessions and devices. Cart abandonment rate is ~70% — most carts are never purchased.
- What about search? Product search with faceted filtering (price range, brand, rating, size) and relevance ranking. Typo tolerance and synonym matching are expected.
Specific Functional Requirements
- Product Catalog: Browse and search millions of products with faceted filtering (category, price, brand, rating)
- Shopping Cart: Add/remove items with real-time pricing and inventory availability, persisted across sessions
- Checkout and Payment: Multi-step checkout with address selection, shipping options, payment processing, and order confirmation
- Inventory Management: Real-time stock tracking across multiple warehouses with reservation during checkout
- Order Management: Order tracking, status updates (confirmed, shipped, delivered), cancellation, and returns
- Flash Sale Handling: Handle sudden traffic spikes of 100x normal load without overselling inventory
- Recommendations: Personalized product suggestions based on browsing history, purchase history, and collaborative filtering
Specific API Endpoints
GET /api/v1/products/search?q=wireless+headphones&category=electronics&price_min=50&price_max=200&sort=relevance&page=1
Response: { "products": [...], "facets": { "brands": [...], "price_ranges": [...] }, "total": 1234 }
POST /api/v1/cart/items
Body: { "product_id": "p_123", "quantity": 2, "variant_id": "v_456" }
Response: { "cart": { "items": [...], "subtotal": 15998, "item_count": 3 } }
POST /api/v1/orders/checkout
Headers: { "Idempotency-Key": "checkout_abc123" }
Body: { "cart_id": "c_789", "shipping_address_id": "a_012", "payment_method_id": "pm_345", "shipping_option": "standard" }
Response: { "order_id": "o_678", "status": "confirmed", "total": 17498, "estimated_delivery": "2025-02-05" }
GET /api/v1/orders/:order_id/track
Response: { "status": "shipped", "tracking_number": "1Z999AA10123456784", "carrier": "UPS", "estimated_delivery": "2025-02-05", "timeline": [...] }
Specific Data Model
Products (Elasticsearch for search + PostgreSQL for source of truth)
| Column | Type | Notes |
|---|---|---|
| product_id | UUID | Primary key |
| title | VARCHAR | Searchable |
| description | TEXT | Searchable with full-text index |
| category_path | ARRAY | ["Electronics", "Headphones", "Wireless"] |
| price_cents | INT | Current price in smallest currency unit |
| brand | VARCHAR | |
| rating_avg | DECIMAL | Denormalized for fast filtering |
| rating_count | INT |
Inventory (PostgreSQL with row-level locking)
| Column | Type | Notes |
|---|---|---|
| product_id | UUID | |
| warehouse_id | UUID | |
| available_count | INT | Current available stock |
| reserved_count | INT | Reserved during checkout (TTL: 15 min) |
| CHECK | available_count >= 0 (database-level constraint prevents overselling) |
Shopping Cart (Redis with PostgreSQL backup)
- Redis for fast reads during active sessions (TTL: 7 days)
- PostgreSQL for persistence across long sessions and cart recovery emails
- Key: user_id or session_id, Value: { items: [{ product_id, quantity, variant_id, added_at }] }
Flash Sale Strategy: Use a queue-based approach. When the sale starts, put all requests into a Kafka topic. A consumer processes them sequentially, decrementing inventory atomically. Users see "processing your request" until their position is reached. This serializes inventory updates and prevents overselling.
Specific Back-of-the-Envelope Numbers
Traffic (Amazon-scale):
- 300M+ active customers, 2.5B product visits/month
- Search queries: 30,000/second average, 100K/second during sales events
- Add to cart: 5,000/second average
- Orders: 66,000/hour normal = ~18 orders/second, 300K/hour during Prime Day = ~83 orders/second
Storage:
- Product catalog: 350M+ products * 5 KB metadata = 1.75 TB
- Product images: 350M * 8 images * 500 KB = 1.4 PB
- Order history: 5B+ orders * 2 KB = 10 TB (must retain for years)
- Search index (Elasticsearch): 350M products * 2 KB indexed = 700 GB (fits in an ES cluster)
Inventory:
- 350M products * average 10 warehouses carrying each = 3.5B inventory records
- Flash sale: 100K concurrent requests for 1,000 units = must process in under 30 seconds
- Inventory check: must be under 10ms (Redis cache in front of database)
Cart:
- 300M active carts in Redis * 1 KB average = 300 GB
- 70% cart abandonment rate = 210M carts never check out
- Cart reminder emails sent 24 hours after abandonment recover ~5-10% of abandoned carts