SYSTEM_DESIGN
System Design: Shopping Cart System
System design of a high-availability shopping cart service supporting guest and authenticated users, cart merging, and seamless handoff to checkout at scale.
Requirements
Functional Requirements:
- Users add, update, and remove items from a shopping cart
- Support guest carts (pre-login) with merge on authentication
- Cart persists across sessions and devices for logged-in users
- Real-time price and availability validation on cart view
- Cart supports promo codes, gift cards, and bundled discounts
- Save-for-later functionality separate from active cart
Non-Functional Requirements:
- Support 50M DAU with 30% cart interaction rate = 15M active carts
- Cart operations (add/update/remove) under 50ms (p99)
- 99.99% availability — cart loss directly impacts revenue
- Cart data must survive server restarts and regional failovers
- Handle flash sale bursts: 100K concurrent cart additions for a single product
Scale Estimation
15M active carts/day with an average of 5 cart operations per session: 75M cart operations/day = 868 ops/sec average, spiking to 50K ops/sec during flash sales. Cart size: average 4 items × 500 bytes = 2KB per cart; 15M carts × 2KB = 30GB active cart data. Guest carts (60% of traffic) with 24-hour TTL add another 20GB. Total working set: 50GB — fits comfortably in a Redis cluster.
High-Level Architecture
The Shopping Cart Service uses Redis as the primary data store for active carts, backed by a PostgreSQL persistence layer for long-term cart storage. The architecture: Client → API Gateway → Cart Service (stateless application tier) → Redis Cluster (primary store) + PostgreSQL (durable backing store). Cart operations follow a write-through pattern: every write updates Redis first (for speed) and asynchronously persists to PostgreSQL via a Kafka event queue.
Guest carts are identified by a UUID stored in a browser cookie. When a user logs in, the Cart Merge Service retrieves both the guest cart (by cookie UUID) and the authenticated cart (by user_id), merges them using configurable rules (keep higher quantity, prefer authenticated cart prices), and deletes the guest cart. The merged cart is written back to Redis under the user_id key.
Cart validation runs on every cart view: the Cart Service makes parallel calls to the Product Catalog Service (price check) and Inventory Service (availability check), updating the cart with current prices and flagging out-of-stock items. This ensures users always see accurate information before checkout.
Core Components
Redis Cart Store
Carts are stored as Redis hashes: key = cart:{user_id} or cart:guest:{uuid}, with hash fields for each cart item (field = product_id:variant_id, value = JSON with quantity, added_price, added_at). Redis hash operations (HSET, HDEL, HGETALL) provide O(1) per-item operations. Cart-level metadata (promo_code, currency, last_updated) is stored in a separate hash key cart_meta:{id}. TTL is set to 30 days for authenticated carts and 24 hours for guest carts, renewed on every access.
Cart Merge Service
Cart merging on login is a critical user experience feature. The merge algorithm: (1) Retrieve guest cart items and authenticated cart items; (2) For items in both carts, take the higher quantity (user likely intended to add, not replace); (3) For items only in the guest cart, add to authenticated cart; (4) Validate merged cart against current inventory; (5) Apply the best promo code from either cart; (6) Delete the guest cart. The merge is atomic — wrapped in a Redis MULTI/EXEC transaction to prevent partial merges on failure.
Price & Availability Validator
Every cart view triggers a validation pass. The validator fetches current prices and stock levels via batch API calls to the Product Catalog and Inventory services. If a price has changed, the cart item is updated with the new price and a price_changed flag is set for the UI to display a notification. If an item is out of stock, it's moved to a cart_unavailable:{id} key and the UI shows a restoration option. Validation results are cached for 60 seconds to avoid hammering upstream services on rapid page refreshes.
Database Design
Redis data model: Hash cart:{user_id} with fields {product_id}:{variant_id} → {"qty": 2, "price_at_add": 29.99, "added_at": "2025-01-15T10:00:00Z"}. Hash cart_meta:{user_id} → {"promo_code": "SAVE10", "currency": "USD", "updated_at": "..."}. Set cart_unavailable:{user_id} → product IDs that went out of stock.
PostgreSQL backing store: carts table (cart_id, user_id UNIQUE, guest_uuid, items JSONB, metadata JSONB, created_at, updated_at). The JSONB items column mirrors the Redis hash structure for easy restore-on-cache-miss. A nightly cleanup job deletes carts older than 90 days. An analytics events table logs cart operations (add, remove, abandon, checkout) for funnel analysis.
API Design
POST /api/v1/cart/items— Add item to cart; body contains product_id, variant_id, quantity; returns updated cart with validated pricesPATCH /api/v1/cart/items/{product_id}— Update item quantity; body contains new quantity; returns updated cartDELETE /api/v1/cart/items/{product_id}— Remove item from cart; returns updated cartPOST /api/v1/cart/merge— Merge guest cart into authenticated cart; body contains guest_cart_uuid; returns merged cart
Scaling & Bottlenecks
Flash sale cart additions create a hot-key problem in Redis: 100K users adding the same product simultaneously all write to their individual cart hashes, but the Inventory Service receives 100K concurrent stock check requests for the same product. The solution: (1) Cart additions are accepted optimistically without inventory checks (validation happens at cart view and checkout); (2) The inventory check uses a Redis-cached stock count with a 5-second TTL, reducing upstream calls by 99%.
Redis cluster scaling uses consistent hashing with 16,384 hash slots across 6 primary nodes (3 replicas). Cart keys are distributed evenly by user_id hashing. For regional failover, Redis Cluster runs in active-passive mode across 2 regions with asynchronous replication; in case of primary region failure, the passive region is promoted and PostgreSQL serves as the recovery source for any carts lost during the replication lag window.
Key Trade-offs
- Redis over pure database: Sub-millisecond cart operations at the cost of memory expense — 50GB in RAM is affordable given the revenue impact of fast cart experience
- Write-through to PostgreSQL: Durability guarantee against Redis failures, but adds write latency (mitigated by async writes via Kafka)
- Optimistic cart additions without inventory check: Allows instant cart response during flash sales, but users may discover items are out of stock at checkout — acceptable trade-off since checking at add-time would create unacceptable latency
- 30-day TTL on authenticated carts: Balances storage cost with user expectation — losing a month-old cart is acceptable, but losing yesterday's cart destroys trust
GO DEEPER
Master this topic in our 12-week cohort
Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.