List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
Assume we have 1 billion DAU.
On each day, assume each user places on average 2 orders, browse their feed 10 times, view/edit carts 20 times, and make payment 2 times.
Assuming peak QPS is twice the average QPS.
Peak QPS for feed serving = 1 billion * 10 / 3600 / 24 = 231K
Peak QPS for order placement/checkout/payment = 1 billion * 2 /3600/24 = 46K
Peak QPS for viewing/editing carts = 1 billion * 20 / 3600 / 24 = 462K
For storage, there are 3 big pieces of storage we need:
For 1 billion users, assuming each user's metadata storage is 5KB, we need 5TB of storage.
For orders, assuming each order's metadata is 5KB. On each new day, we need 10TB of storage. For orders that are more than 1 year old, we place them in cold storage.
For the e-commerce platform, assume we have 5 billion e-commerce items, where each item's metadata is 1KB, but with 5MB of media.
We need 5TB of metadata storage, and 5PB of object storage.
Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
1. Product Search & Browse
| Method | Endpoint | Description |
| GET | /api/v1/products | Search/filter products |
| GET | /api/v1/products/{id} | Get product details |
| GET | /api/v1/categories | List categories |
Query params for /products: ?q=keyword&category_id=5&min_price=10&max_price=100&sort=rating&page=1&size=20
Response:
{ "products": [{ "id": "p123", "name": "Nike Air Max", "price": 129.99, "image_url": "...", "rating": 4.5, "inventory_status": "in_stock" }], "total": 342, "page": 1, "size": 20}
2. Cart Management (Major)
| Method | Endpoint | Description |
| POST | /api/v1/cart | Add item to cart |
| GET | /api/v1/cart | View cart contents |
| PATCH | /api/v1/cart/{item_id} | Update quantity |
| DELETE | /api/v1/cart/{item_id} | Remove item |
Add-to-cart body: { "product_id": "p123", "quantity": 2 }
3. Checkout & Orders (Major)
| Method | Endpoint | Description |
| POST | /api/v1/checkout | Place order |
| GET | /api/v1/orders/{id} | Order status |
| GET | /api/v1/orders | Order history |
Checkout body: { "shipping_address_id": "addr456", "payment_method_id": "pm789" }
4. User Auth
| Method | Endpoint |
| POST | /api/v1/auth/register |
| POST | /api/v1/auth/login |
Returns JWT used in Authorization: Bearer <token> header.
Idempotency-Key header) to prevent double charges on retryDescribe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
All requests to the system will go through API gateway, used for authentication and rate limiting, and load balancers, which distributes requests to servers evenly based on a consistent hashing of user_ids.
For user registeration/login, we store user_name and salted + hashed password to the relational database. During login, we check credentials from the database. Upon success, and give user JWT tokens for later authentication.
For item ingestion, when each merchant on the platform updates their items, we trigger events on the kafka queue, and write the updated item and inventory information to the relational database. Another writer also ingests the events, and write the items to ElasticSearch cluster. This is because item inventory and category updates from the merchant side don't need to be real-time. To prevent it from overwhelming the storage engines, we use Kafka message queue as a buffering.
For item browsing, we do the below:
With this approach, each storage engine is used for what it's best for.
Also, we call recommendation service to use our internal ML models to recommend items to each user.
For cart management, given we store each user's cart in redis and the underlying relational databse, we query the cart contents/status from it. For any updates we make, we also update redis and the relational database synchronously. One thing to note here is for any retry, we need to use the same idempotent key, so we avoid double-adding or double-removing items from a user's cart.
For checkout/order placement, once we receive the request from client. We trigger the payment from external client provider. Upon successful, we trigger updates on the kafka queue, and update:
This is an eventually consistent system. It is an acceptable tradeoff for lower latency and better availability for users.
For viewing past orders and current order status, we query redis cluster and the underlying relational database.
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
For this system, we should store the below information:
We should first choose the storage engine for each.
We should first consider their required consistency levels and write throughput.
For users, orders and user carts, we should have strong consistency. Because inconsistent user profiles, order status/history and order carts, will cause huge confusion and inconvenience to users. For items, we also need strong consistency since we need to track the inventory count for each item. And this needs to be atomic updates.
Also, we need complex join relationships between users, orders and carts. Under the circumstance, we will use a distributed SQL database for them, while using cassandra for items. The tradeoff is relational database is more complex to scale horizontally. However, we do need the strong ACID guarantees provided by SQL databases.
Here's some sample schema.
table users {
user_id: UUID,
user_name: String,
user_profile_pic_link: String,
user_age: Int,
user_region: String,
user_email: String,
user_hashed_password: String,
user_address: String,
user_phone: String,
user_account_created_at: Timestamp,
user_membership_info: String
}
We can shard this table through consistent hashing of user_ids, which should distribute users evenly to each shard. Each shard will also have read replicas which gets written to synchronously in writes, but which can help to spread out the read load.
table orders {
order_id: UUID,
user_id: UUID,
store_id: UUID,
order_placed_at: Timestamp,
order_items: json_string,
order_status: String,
order_subtotal: String,
payment_method: String
}
The sharding key is order_id, replication strategy same as above.
table carts {
cart_id: UUID,
user_id: UUID,
store_id: UUID,
cart_opened_at: Timestamp,
cart_items: json_string,
cart_status: String,
cart_subtotal: String,
}
The sharding key is cart_id, replication strategy same as above.
table items {
item_id: UUID,
item_name: String,
item_image_url: String,
item_price: Int,
item_description: String,
item_deal: String,
item_tags: String,
item_available: boolean,
merchant_id: UUID,
item_inventory_count: Int
}
The sharding key is item_id, replication strategy same as above.
We will also use redis cluster as caching layer to support faster browse and discovery. The cache will be write-through to maintain consistency and durability of data.
We will use ElasticSearch to support search and filtering of items.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
For this system, we should store the below information:
We should first choose the storage engine for each.
We should first consider their required consistency levels and write throughput.
For users, orders and user carts, we should have strong consistency. Because inconsistent user profiles, order status/history and order carts, will cause huge confusion and inconvenience to users. For items, we also need strong consistency since we need to track the inventory count for each item. And this needs to be atomic updates.
Also, we need complex join relationships between users, orders and carts. Under the circumstance, we will use a distributed SQL database for them, while using cassandra for items. The tradeoff is relational database is more complex to scale horizontally. However, we do need the strong ACID guarantees provided by SQL databases.
Here's some sample schema.
table users {
user_id: UUID,
user_name: String,
user_profile_pic_link: String,
user_age: Int,
user_region: String,
user_email: String,
user_hashed_password: String,
user_address: String,
user_phone: String,
user_account_created_at: Timestamp,
user_membership_info: String
}
We can shard this table through consistent hashing of user_ids, which should distribute users evenly to each shard. Each shard will also have read replicas which gets written to synchronously in writes, but which can help to spread out the read load.
table orders {
order_id: UUID,
user_id: UUID,
store_id: UUID,
order_placed_at: Timestamp,
order_items: json_string,
order_status: String,
order_subtotal: String,
payment_method: String
}
The sharding key is order_id, replication strategy same as above.
table carts {
cart_id: UUID,
user_id: UUID,
store_id: UUID,
cart_opened_at: Timestamp,
cart_items: json_string,
cart_status: String,
cart_subtotal: String,
}
The sharding key is cart_id, replication strategy same as above.
table items {
item_id: UUID,
item_name: String,
item_image_url: String,
item_price: Int,
item_description: String,
item_deal: String,
item_tags: String,
item_available: boolean,
merchant_id: UUID,
item_inventory_count: Int
}
The sharding key is item_id, replication strategy same as above.
We will also use redis cluster as caching layer to support faster browse and discovery. The cache will be write-through to maintain consistency and durability of data.
We will use ElasticSearch to support search and filtering of items.
All requests to the system will go through API gateway, used for authentication and rate limiting, and load balancers, which distributes requests to servers evenly based on a consistent hashing of user_ids.
For user registeration/login, we store user_name and salted + hashed password to the relational database. During login, we check credentials from the database. Upon success, and give user JWT tokens for later authentication.
For item ingestion, when each merchant on the platform updates their items, we trigger events on the kafka queue, and write the updated item and inventory information to the relational database. Another writer also ingests the events, and write the items to ElasticSearch cluster. This is because item inventory and category updates from the merchant side don't need to be real-time. To prevent it from overwhelming the storage engines, we use Kafka message queue as a buffering.
For item browsing, we do the below:
With this approach, each storage engine is used for what it's best for.
Also, we call recommendation service to use our internal ML models to recommend items to each user.
For cart management, given we store each user's cart in redis and the underlying relational databse, we query the cart contents/status from it. For any updates we make, we also update redis and the relational database synchronously. One thing to note here is for any retry, we need to use the same idempotent key, so we avoid double-adding or double-removing items from a user's cart.
For checkout/order placement, once we receive the request from client, we trigger the payment from external client provider. Upon successful, we trigger updates on the kafka queue, and update:
This is an eventually consistent system. It is an acceptable tradeoff for lower latency and better availability for users.
To prevent double booking of the last item:
1. Checkout Flow with Inventory Reservation
1. User clicks "Place Order" 2. POST /checkout (with idempotency key) 3. Order Service calls Inventory Service: RESERVE(product_id, quantity) - Inventory Service runs: UPDATE items SET reserved = reserved + 2 WHERE id = 'p123' AND (available - reserved) >= 2 - If rows_affected == 0 → fail fast, return "Out of stock" 4. If reserved OK → proceed to Payment Service 5. Payment succeeds → Inventory Service: CONFIRM(product_id, quantity) - UPDATE items SET available = available - 2, reserved = reserved - 2 6. Payment fails → Inventory Service: RELEASE(product_id, quantity) - UPDATE items SET reserved = reserved - 2
The key insight: reserve BEFORE payment. The atomic WHERE clause prevents overselling regardless of concurrency.
2. Failure Handling
| Scenario | Action |
| Payment fails after reserve | Call RELEASE to decrement reserved |
| Payment succeeds, order write fails | Saga pattern — emit OrderFailed event → consumer calls RELEASE |
| Idempotent retry on checkout | Use Idempotency-Key — check if reservation already exists, skip if so |
| Timeout / no response from payment | Set TTL on reservation (e.g., 15 min) — background job releases expired reservations |
The Saga pattern with a compensating transaction (RELEASE) is the standard approach here. Kafka events ensure eventual consistency — the order service publishes OrderConfirmed or OrderFailed, and the inventory consumer reacts accordingly.
For viewing past orders and current order status, we query redis cluster and the underlying relational database.