Interview Setup
Interview Prompt
Design an e-commerce shopping cart system supporting add, update, and remove actions, guest and logged-in carts, login cart merging, and price validation at checkout.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| Guest vs logged-in storage strategy? | 20M guest carts per day using client localStorage and Redis compared to 50M persistent user carts in Redis. |
| Price capture at add time vs checkout? | Price at add time is recorded in the cart line item, whereas checkout revalidates against the catalog service. |
| Cart durability requirements? | Redis serves as primary fast store with asynchronous PostgreSQL backups every 5 minutes for disaster recovery. |
| Peak cart operations per second? | Average 6K operations per second scaling to 50K during promotional sales determines Redis cluster shard sizing. |
Scope
In scope
- Add, update, and remove cart items
- Guest cart management
- Login cart merge
- Checkout price validation
- Save for later wishlist
- Cart abandonment events
Out of scope (state explicitly)
- Inventory reservation lifecycle
- Payment processing
- Recommendations
Functional Requirements
Start by aligning with your interviewer on the system scope. For an e-commerce shopping cart, confirm guest vs logged-in behaviors, cart merge semantics on login, and whether price revalidation or checkout inventory reservation belongs in-band.
- Add and remove items: Add products with specified quantities, update quantities dynamically, and remove items.
- Persist cart across sessions: Ensure a logged-in user cart survives application termination and synchronizes across multiple devices.
- Guest cart support: Allow anonymous visitors to accumulate items without authentication and merge items cleanly upon login.
- Price and availability validation: Recheck catalog prices and inventory stock at checkout time because carts can sit idle for weeks before purchase. For stock reservation mechanics, see the Inventory Management System.
- Saved for later: Allow users to move items out of the active cart into a persistent secondary wishlist.
- Configurable cart expiry: Automatically expire abandoned guest and user carts according to retention policies (such as 30 days).
- Promotions and discounts: Apply coupon codes, bundle pricing, and display itemized order discounts.
- Multi-seller carts: Support items sourced from multiple vendors within one cart, displaying per-seller subtotals before handoff to the Order Management System.
- Shipping estimation: Calculate estimated shipping fees and delivery timelines based on item dimensions and destination zip code.
- Cart sharing: Allow customers to export and share read-only cart links for gift registries and collaboration.
Non-Functional Requirements
Interviewers focus primarily on low-latency mutations and consistent cross-device synchronization. Introducing an in-memory key-value pattern early justifies sub-50 ms updates without tying connections to session affinity.
- Low Latency: Process cart mutations (add, update, delete) in under 50 ms p99, with cart read latencies under 5 ms.
- High Availability: Achieve 99.99% uptime because cart service degradation immediately stalls checkout conversion and revenue.
- Consistency: Guarantee real-time cross-device consistency for logged-in accounts using centralized session storage.
- Scale: Support 50M daily active cart users and handle 500M daily operations, scaling up to 50K operations per second during major events such as a Flash Sale System.
- Durability: Ensure cart state survives Redis server restarts via append-only file (AOF) persistence and periodic relational database snapshots.
- Stateless Application Layer: Eliminate sticky sessions so any API gateway or cart worker instance can serve any incoming request.
- Idempotency: Ensure duplicate network requests or retries do not inadvertently increment item quantities.
Capacity Estimations
Calculate memory and throughput bounds before finalizing the cluster topology. Active daily users and operations per user determine partition count, while average item volume dictates memory sizing.
| Metric | Calculation | Value |
|---|---|---|
| DAU with active carts | Given (assumption documented in value) | 50M |
| Cart operations / day | 50M users x ~10 ops/day | 500M |
| Cart operations / sec | 500M ÷ 86400 | ~6K (peak 50K during sales) |
| Avg items per cart | Given (typical workload assumption) | 5 |
| Cart data per user | Given | ~2 KB |
| Total cart data | 50M x 2 KB | 100 GB |
| Guest carts / day | Given (assumption documented in value) | 20M (many abandoned) |
| Cart-to-order conversion | Given | ~10% |
Architecture Diagram
In the room: mention the guest-to-authenticated cart merge early, because atomic Lua scripts on login represent a classic follow-up topic.
Guide the interviewer through the guest vs logged-in architectural split. Authenticated carts reside directly in Redis for fast reads, whereas anonymous guests retain state on-device until login triggers an atomic merge. Final checkout rechecks prices and item stock against catalog and inventory clusters, separated into a dedicated validation hop.
Component Deep Dives
Cart Storage Architecture: Redis Hash Pattern
In-Memory Key-Value Storage
Cart reads occur on almost every storefront page navigation, so authenticated cart data is maintained in Redis Hashes with sub-millisecond access. For a broader overview of caching tiers, review Redis Patterns and Caching Patterns. Anonymous guest carts remain on the client device until login triggers an atomic Lua script merge into the primary user key.
Event Bus Design (Kafka)
Cart state changes stream asynchronously to Kafka for analytics and relational snapshots, completely decoupled from the latency-sensitive add-to-cart path.
Topic: cart-events
Partitions: 64
Partition key: user_id (preserves per-user cart event ordering)
Retention: 7 days
Replication factor: 3, min.insync.replicas: 2
Producer: Cart Service on mutation (idempotent producer)
Event: { event_id, user_id, action: "add" | "remove" | "update" | "checkout" | "abandon", items[], total_value, timestamp }
Consumer groups:
1. abandonment-recovery: triggers recovery emails if no checkout occurs within 1 hour (suppressed if purchased or cart < $10)
2. analytics: ingests cart funnels into ClickHouse to evaluate drop-offs
3. recommendation: mines "frequently added together" co-occurrence patterns
4. pg-backup: asynchronous snapshots from Redis cart to PostgreSQL every 5 minutes
Sync path: cart CRUD in Redis executes in under 5 ms; cart service publishes event asynchronously.
Async path: abandonment emails and analytics processing never block the add-to-cart request.
DLQ: cart-events-dlq, alerting when abandonment consumer lag exceeds 300 seconds.Guest Cart to Login Merge
Atomic Merge Workflow
Merging guest carts into existing user carts is a critical edge case. Interviewers expect an atomic transaction model rather than vulnerable read-modify-write patterns that risk race conditions.
When a guest with 3 items signs in to an account that already contains 2 items, the service merges entries by taking the maximum quantity for overlapping SKUs. Executing this logic via a Redis Lua script ensures complete atomicity against concurrent login sessions.
Scenario:
1. Guest adds 3 items to cart (stored by session_id):
cart:guest-session-abc -> { SKU-1: qty 1, SKU-2: qty 2, SKU-3: qty 1 }
2. Guest logs in as user-123 with existing cart:
cart:user-123 -> { SKU-2: qty 1, SKU-4: qty 3 }
3. Merge strategy:
For items present in BOTH carts (SKU-2):
Option A: Keep higher quantity -> qty = max(2, 1) = 2
Option B: Sum quantities -> qty = 2 + 1 = 3
Option C: Retain guest cart quantity as most recent intent -> qty = 2
Standard e-commerce convention selects Option A (max quantity) for optimal user experience.
For items only in guest cart: insert into user cart
For items only in user cart: retain unchanged
Result: cart:user-123 -> { SKU-1: qty 1, SKU-2: qty 2, SKU-3: qty 1, SKU-4: qty 3 }
4. Delete guest cart: DEL cart:guest-session-abcPrice Validation and Stale Data Handling
Checkout Price Verification
Shopping carts frequently sit untouched for days or weeks, making product price changes and inventory depletion inevitable. Clarify your stale-pricing policy before detailing batch lookup flows.
The system adopts a hybrid resolution policy: capture item price at initial add time, then reconcile against current catalog rates during checkout. If the current price is lower, the customer receives the discount. If higher, the UI flags the increase and prompts confirmation before charging.
Implementation:
On cart page load:
cart_items = redis.hgetall("cart:user-123")
current_prices = catalog_service.batch_get_prices([sku_ids])
for item in cart_items:
item.current_price = current_prices[item.sku_id]
item.price_changed = (item.current_price != item.price_at_add)
item.in_stock = inventory_service.check_stock(item.sku_id)
return cart with annotationsAPI Design
Cart Mutation and Query Endpoints
All endpoints enforce idempotent semantics. Line items identify by product SKU, and modifications validate through standard REST verbs.
Add to Cart
POST /api/v1/cart/items
{
"sku_id": "SKU-456",
"quantity": 2,
"seller_id": "seller-1"
}
Response: 200 OK
{
"cart_id": "cart:user-123",
"item_count": 5,
"item": {"sku_id": "SKU-456", "quantity": 2, "price": 29.99, "seller_id": "seller-1"}
}Get Cart
GET /api/v1/cart
Response: 200 OK
{
"items": [
{"sku_id": "SKU-456", "name": "Wireless Mouse", "quantity": 2,
"price_at_add": 29.99, "current_price": 29.99, "price_changed": false,
"in_stock": true, "seller": "TechStore", "image": "https://cdn..."}
],
"subtotal": 59.98,
"item_count": 5,
"promotions_applied": [{"code": "SAVE10", "discount": -5.99}],
"estimated_total": 53.99
}Update Item Quantity
PUT /api/v1/cart/items/{sku_id}
{ "quantity": 3 }
Response: 200 OKRemove Item
DELETE /api/v1/cart/items/{sku_id}
Response: 200 OKMerge Guest Cart
POST /api/v1/cart/merge
{ "guest_session_id": "session-abc" }
Response: 200 OK
{ "merged_items": 3, "conflicts_resolved": 1 }Common Error Responses
The cart service surfaces structured client error payloads for malformed items, expired sessions, and validation failures.
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff 402 Payment Required: account balance or payment method has insufficient funds 502 Bad Gateway: payment gateway provider timeout, poll transaction status endpoint
Data Model
Redis Primary Cart Store
User and guest carts utilize Redis Hash structures to provide O(1) mutations and reads per line item without serializing the full cart array.
# User cart (Hash)
cart:{user_id} -> Hash {
SKU-456: '{"qty":2,"price_at_add":29.99,"seller_id":"s-1","added_at":"2026-03-14T10:00:00Z"}',
SKU-789: '{"qty":1,"price_at_add":14.99,"seller_id":"s-2","added_at":"2026-03-14T10:05:00Z"}'
}
TTL: 2592000 (30 days, refreshed on activity)
# Guest cart
cart:guest-{session_id} -> Hash (same structure)
TTL: 86400 (1 day, shorter TTL for anonymous guests)
# Saved for later
saved:{user_id} -> Hash { SKU-123: '{"saved_at":"...","price":29.99}' }
TTL: 7776000 (90 days)
# Cart item count (for badge display to avoid HLEN on every page)
cart_count:{user_id} -> INT
TTL: 2592000PostgreSQL Backup and Analytics Schema
Asynchronous workers persist snapshot records into relational tables to power recovery tooling and cart abandonment analytics.
CREATE TABLE cart_snapshots (
user_id UUID NOT NULL,
sku_id VARCHAR(50) NOT NULL,
quantity INT NOT NULL,
price_at_add DECIMAL(10,2),
seller_id VARCHAR(50),
added_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, sku_id)
);
-- Asynchronous sync from Redis to PostgreSQL every 5 minutes
-- Used for: analytics (abandonment analysis) and Redis disaster recoveryKafka Event Streaming Specifications
Cart state transitions publish to streaming topics to feed recommendation systems and customer recovery campaigns.
Topic: cart-events (actions: add, remove, update, checkout, abandon)
Consumed by: analytics pipeline, recommendation engine ("frequently added together")Fault Tolerance
| Concern | Solution |
|---|---|
| Redis node failure | Redis Cluster with 6 or more nodes, AOF persistence, and data replicated across shards. |
| Redis total failure | Fall back to PostgreSQL backup with slightly stale state, then rebuild Redis keys from PostgreSQL. |
| Cart data loss | Asynchronous backup to PostgreSQL every 5 minutes, with client-side localStorage as last resort. |
| Duplicate add-to-cart | Idempotent mutations because HSET overwrites identical keys instead of creating duplicate records. |
| Price discrepancy | Always validate at checkout and display clear price drift warnings on the cart page. |
| Race condition during merge | Redis Lua script for atomic merge so concurrent merge attempts cannot collide. |
| Abandoned cart recovery | Kafka event published after 1 hour of cart inactivity to trigger notification workflows. |
Additional Considerations
Interview Walkthrough
- 25-minute pacing strategy
Prioritize core architecture before diving into deep multi-region or analytics pipelines.
- Cart modeled as session-scoped Redis Hash with 30-day TTL (5 min)
- Price-at-add semantics stored with each line item (6 min)
- Guest carts with 1-day TTL and atomic Lua merge on login (5 min)
- Add, update, and remove mutations via O(1) Hash operations with version tracking (5 min)
- Asynchronous PostgreSQL snapshots via Kafka for disaster recovery (4 min)
- Frame the cart as a session-scoped, high-churn key-value store using a Redis Hash per user with a 30-day TTL refreshed on activity.
- Explain price-at-add semantics: store the initial price when the item enters the cart, then flag
price_changedon read without silently updating totals. - Cover guest carts with shorter TTL (1 day) and the merge flow on login, resolving quantity conflicts by summing or keeping the higher quantity.
- Walk through add, update, and remove as O(1) Hash operations with a separate
cart_countkey for header badge display without full hash scans. - Detail asynchronous PostgreSQL snapshots via Kafka for analytics and disaster recovery, keeping Redis as primary while PostgreSQL serves as a backup rather than residing on the hot path.
- Discuss multi-seller carts: each line item carries seller identification so checkout can split line items into separate fulfillment orders in the Order Management System.
- Common pitfall: storing the cart in session cookies or JWT tokens introduces strict size limits, prevents server-side cart merging, and eliminates cross-device synchronization.
Engineering Trade-offs
Storage Engine Comparison
Cart storage balances durability against throughput, leveraging Redis for hot mutation paths and PostgreSQL for audit trails and abandoned-cart analytics.
Redis (Recommended): ✓ Sub-millisecond latency ✓ Hash data structure matches cart operations directly ✓ Native TTL for automatic expiration ✓ Lua scripting for atomic operations (merge, validate) ✗ In-memory storage (requires AOF and RDB for persistence) ✗ Higher RAM cost at 100 GB scale DynamoDB: ✓ Fully managed persistence and replication ✓ Pay-per-request pricing suitable for variable loads ✓ Automatic scaling ✗ Higher latency (5 to 10 ms compared to sub-millisecond Redis) ✗ No TTL granularity per hash field (TTL applies per item) ✗ Item size limit of 400 KB Server-side session (e.g., PostgreSQL): ✓ Fully durable with ACID guarantees ✗ Much higher latency (10 to 50 ms) ✗ Frequent cart reads create a database bottleneck ✗ Relational schemas are suboptimal for rapid key-value access Client-side (localStorage): ✓ Zero server cost for guest carts ✓ Operates offline ✗ Does not synchronize across devices ✗ Lost if browser data is cleared ✗ Lacks server-side analytics visibility Best practice: Guest carts: localStorage on client with asynchronous backup to Redis on key actions Logged-in carts: Redis as primary store with asynchronous backup to PostgreSQL This strategy minimizes Redis memory consumption while sustaining low read latency.
Cart Abandonment Strategy
Recovering abandoned carts requires identifying inactivity thresholds and orchestrating targeted notification workflows without generating spam.
Industry benchmark: approximately 70% of shopping carts are abandoned without checkout.
Detection:
Cart contains items with no checkout activity for 1 hour -> Flagged as abandoned.
Kafka event: { user_id, cart_items, total_value, abandoned_at }
Triggers downstream workflows:
1. Email reminder after 1 hour: "You left items in your cart"
2. Push notification after 4 hours: "Your cart is waiting"
3. Email with discount after 24 hours: "10% off your cart with code COMEBACK10"
4. Final reminder after 72 hours: "Items in your cart may sell out soon"
Suppression rules:
- User has opted out of marketing communications.
- Cart total value is under $10 (email cost exceeds expected margin).
- User has completed an order since the abandonment event.
Analytics:
Track conversion rates at each checkout funnel stage (cart to shipping to payment to confirmation).
Identify the specific drop-off step with highest friction.
Conduct A/B testing on cart layouts and incentives to lift conversion rates.Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.