List functional requirements for the system (Ask the chat bot for hints if stuck.)...
Users both consumers and sellers should be able to register and log in on the app.
Grocery store owners should be able to add, update or remove products they offer including details like price, quantity and images.
Consumers should be able to search for products from multiple stores and apply filters such as category, price range and ratings.
Consumers should be able to add items to the cart and checkout the order.
Secure payment processing through various methods such as credit/debit/upi
Consumers should be able to track orders in real time.
Implement notification system to inform users about the order status/updates and promotional offers.
System for managing delivery logistics, such as assigning orders to delivery partners and updating status
Consumers should be able to rate and review the products
Customer support system for issues related to orders/payments.
List non-functional requirements for the system...
Scalability: system should be able to scale vertically and horizontally to accommodate increasing number of store, users and orders.
Performance: The app should respond quickly to user action with pages and responses within a few seconds:
Availability: The app should have high availability, ideally 99.9% uptime.
Reliability: The system should be consistent event under high load. Concurrent transactions should not lead to anomalies.
Security: The user data should be protected through encryption and authentication mechanism, secure payment gateways
User interface
Compatibility: Compatible with various devices: ios, android and web browsers
Maintainability: Easy to maintain and update, quick bug fixes, feature enhancements without dwntime.
Estimate the scale of the system you are going to design...
Lets say initially we are serving in 10 different cities and on an average we have 20 registered grocery stores at each of these 10 locations. Each store lists a total of 100 products roughly so we have 20000 products.
Users: 50000
On an average we get 100 orders per day in one city so we have 1000 orders per day.
Now during peak time lets say our orders double up so we have 2000 orders per day. 10% of the customers do a concurrent request during peak time. So during peak time we have 200 orders coming in at once.
How many servers?
We can assume a 10:1 ratio for read-write in grocery delivery system per order.
Let's estimate that each request roughly needs 250 ms of cpu time. So 0.25 seconds per request so each core can handle 4 requests per second. At peak time we say there are 200 requests coming up in burst in one second. For this we will need 50 concurrent cores. This is for payment.
We might have cart additions and browsing requests also coming up. Lets say we have 200 cart addition requests and 10x i.e. 2000 browsing-read requests. So total of 2200 requests per second. 2200/4 cores=550.
So during peak hours we might need 600 cores.
Assuming we have 8 cores per server we will need 600/8=75 servers during peak traffic. To meet high availability lets say we provision 2x servers so we will need 150 servers. So we can either increase cpu cores during peak hours which might increase the hardware cost or have the number of severs go up to meet high demand.
Data storage
Products: 1KB each
20000KB=20MB
Users: 1KB
50000KB=50MB
Orders: 2KB
1,500 orders/day × 2 = 3 MB/day
3*365MB=1095MB=1gb
Logs¬ifications
10GB
Images
2000*100kb=2gb
Initial storage needed:
Adding buffer for growth we will need approximately 50 gb of storage
Network bandwidth
Peak requests per second: 2,400 (including payment, cart, browsing)
Average request-response size: 5 KB (typical for JSON or REST API responses with product info, orders, etc.)
Calculation
Total data transfer per second (in KB)
2400 requests/sec×5KB/request=
12000=11.7MBps=93.6Mbps
Final Estimate
Peak network bandwidth: ~100 Mbps
Define what APIs are expected from the system...
User Authentication & Registration
POST /login
Authenticate user credentials and return a token.
POST /register
Create a new user account.
Cart APIs
GET /users/{userId}/cart
Retrieve the cart items for the user.
POST /users/{userId}/cart
Add an item to the user’s cart.
DELETE /users/{userId}/cart/{productId}
Remove an item from the cart.
Product APIs
GET /products
Get a list of all products (with filters via query parameters: ?name=apple&category=fruits).
GET /products/{productId}
Get details for a specific product by its ID.
GET /products/search
Search for products by name using a query parameter: /products/search?name=milk.
Order APIs
GET /orders/{orderId}
Retrieve details of a specific order.
PUT /orders/{orderId}
Update the details/status of an order.
DELETE /orders/{orderId}
Delete an order (e.g., by admin or cancellation logic).
POST /orders
Create a new order (i.e., checkout flow).
GET /users/{userId}/orders
Get a list of all orders for a user.
Additional APIs (if needed)
GET /stores/{storeId}/products
Get all products for a specific store.
POST /products
For store owners to create new products.
PUT /products/{productId}
Update product information.
DELETE /products/{productId}
Remove a product.
Defining the system data model early on will clarify how data will flow among different components of the system. Also you could draw an ER diagram using the diagramming tool to enhance your design...
User:
| FieldTypeDescription | ||
| user_id | UUID/PK | Unique identifier for the user |
| name | VARCHAR | User’s name |
| VARCHAR | Unique email address | |
| password_hash | VARCHAR | Hashed password |
| phone | VARCHAR | Phone number |
| address | TEXT | Delivery address |
| created_at | TIMESTAMP | Account creation date |
| store_id | UUID/PK | Unique identifier for the store |
| name | VARCHAR | Store name |
| owner_id | UUID/FK | Reference to User (store owner) |
| city | VARCHAR | City/location |
| address | TEXT | Store address |
| created_at | TIMESTAMP | When store was created |
Products
| product_id | UUID/PK | Unique product ID |
| store_id | UUID/FK | Reference to Store |
| name | VARCHAR | Product name |
| description | TEXT | Description |
| price | DECIMAL | Product price |
| image_url | VARCHAR | URL to product image |
| stock_qty | INT | Current stock quantity |
| created_at | TIMESTAMP | When product was listed |
Cart
| cart_id | UUID/PK | Unique identifier for the cart |
| user_id | UUID/FK | Reference to User |
| created_at | TIMESTAMP | When cart was created |
Cart Item
| cart_item_id | UUID/PK | Unique cart item ID |
| cart_id | UUID/FK | Reference to Cart |
| product_id | UUID/FK | Reference to Product |
| quantity | INT | Quantity of the product in cart |
Order
| order_id | UUID/PK | Unique order identifier |
| user_id | UUID/FK | Reference to User |
| store_id | UUID/FK | Store fulfilling the order |
| status | VARCHAR | (Pending, Processing, Delivered) |
| total_amount | DECIMAL | Total order amount |
| payment_method | VARCHAR | (UPI, card, COD) |
| created_at | TIMESTAMP | When the order was placed |
Order Items:
| order_item_id | UUID/PK | Unique order item ID |
| order_id | UUID/FK | Reference to Order |
| product_id | UUID/FK | Reference to Product |
| quantity | INT | Quantity ordered |
| price | DECIMAL | Price at the time of ordering |
Review:
| review_id | UUID/PK | Unique identifier for the review |
| product_id | UUID/FK | Product being reviewed |
| user_id | UUID/FK | User who wrote the review |
| rating | INT | Numeric rating (1-5) |
| comment | TEXT | Optional comment |
| created_at | TIMESTAMP | When the review was created |
Notification:
| notification_id | UUID/PK | Unique notification ID |
| user_id | UUID/FK | User to notify |
| message | TEXT | Notification message |
| status | VARCHAR | (Unread, Read) |
| created_at | TIMESTAMP | When the notification was created |
Relationships (Key Points)
One User → Many Orders, Many Carts, Many Reviews, Many Notifications
One Store → Many Products, Many Orders
One Order → Many OrderItems
One Cart → Many CartItems
One Product → Many Reviews, Many CartItems, Many OrderItems
You should identify enough components that are needed to solve the actual problem from end to end. Also remember to draw a block diagram using the diagramming tool to augment your design. If you are unfamiliar with the tool, you can simply describe your design to the chat bot and ask it to generate a starter diagram for you to modify...
User-Facing Applications
Mobile Apps: Android, iOS for consumers and store owners
Web Application: Accessible by store owners and delivery partners
API Gateway
Single entry point for all client requests
Handles authentication, routing, rate-limiting, load balancing
Authentication Service
Manages registration, login, and token validation
Product Catalog Service
Manages product listings, categories, search
Cart Service
Manages items in the user’s cart
Order Management Service
Handles order creation, updates, cancellations
Coordinates with payment and delivery logistics
Payment Service
Processes secure payments via external payment gateways
Delivery Logistics Service
Assigns delivery partners and tracks delivery status
Notification Service
Sends push notifications, emails, and SMS for order status and promotions
Data Stores
User Database: User profiles, addresses
Product Database: Product listings, store info
Cart Database: User-specific cart items
Order Database: Order records
Notification Database: Notification records
External Services
Payment Gateway: e.g., Razorpay, Stripe
Delivery Partner APIs: e.g., logistics partners
Push Notification Services: e.g., Firebase
Monitoring & Logging
Collects logs, metrics for observability (e.g., Prometheus, Grafana)
Explain how the request flows from end to end in your high level design. Also you could draw a sequence diagram using the diagramming tool to enhance your explanation...
1. User Browses and Adds Items to Cart
The user opens the app and browses products.
The client app sends a GET request to the API Gateway for products.
The API Gateway forwards this to the Product Catalog Service.
The Product Catalog Service queries the Product DB and returns product listings.
The user adds products to the cart.
The client app sends a POST or PUT request to the API Gateway.
The API Gateway forwards it to the Cart Service, which updates the Cart DB.
2. User Places Order (Checkout)
The user initiates checkout.
The client sends a POST request to the API Gateway.
The API Gateway forwards this to the Order Management Service.
The Order Management Service verifies the cart and calculates the total amount.
It initiates a payment request to the Payment Service.
The Payment Service calls the external Payment Gateway.
On successful payment, the Order Management Service saves the order in the Order DB.
It then informs the Delivery Logistics Service to assign a delivery partner.
The Delivery Logistics Service updates delivery status and uses Delivery Partner APIs.
The Notification Service is triggered to notify the user about order confirmation and delivery updates.
Notifications are saved in the Notification DB and sent via push/email/SMS.
Dig deeper into 2-3 components and explain in detail how they work. For example, how well does each component scale? Any relevant algorithm or data structure you like to use for a component? Also you could draw a diagram using the diagramming tool to enhance your design...
1. Product Catalog Service
Responsibilities
Manage product listings (CRUD operations).
Support product search, filtering, and sorting.
Scalability
Horizontal Scaling: Deploy multiple replicas of the Product Catalog Service to handle high read loads.
Caching Layer: Use a distributed cache (like Redis) to store frequently requested product data. This reduces database queries for common requests.
Indexing: Create database indexes on commonly queried fields (name, category, storeId, price) to speed up queries.
Data Structures
Product data stored in a document or relational database.
Example (in a document-oriented format):
{
"productId": "p123",
"name": "Organic Milk",
"storeId": "s10",
"category": "Dairy",
"price": 50,
"availableQuantity": 200,
"imageUrl": "http://example.com/milk.jpg"
}
Algorithms and Techniques
Search: Use a text-based search engine (like Elasticsearch) or Postgres full-text search for flexible product queries.
Cache Invalidation: Invalidate or update cache entries when a product’s details change to ensure consistency.
Data Consistency: Leverage eventual consistency where appropriate, since product data can tolerate slight delays in updates for improved performance.
2. Cart Service
Responsibilities
Manage user carts, including adding/removing items and updating quantities.
Provide cart state during checkout.
Scalability
Session-based Caching: Store active cart data in a fast in-memory cache (like Redis), reducing database writes for frequent updates.
Database Partitioning: Use sharding based on userId to distribute cart data across database nodes.
Data Structures
Cart data typically structured as:
{
"cartId": "c789",
"userId": "u456",
"items": [
{ "productId": "p123", "quantity": 2 },
{ "productId": "p987", "quantity": 1 }
],
"lastUpdated": "2025-06-07T15:00:00Z"
}
Use a hash structure in Redis to store each user’s cart for fast read/write.
Algorithms and Techniques
Idempotent Updates: Updating the quantity of an item overwrites the existing entry, preventing duplication.
Cart Expiration: Use a TTL (time-to-live) to expire inactive carts in Redis, saving resources.
3. Order Management Service
Responsibilities
Handle order placement, updates, cancellations, and tracking.
Coordinate with payment and delivery services.
Scalability
Microservice Replication: Deploy multiple instances behind a load balancer to handle spikes in order placement.
Asynchronous Processing: Use message queues (like Kafka or RabbitMQ) for tasks like payment processing and delivery assignment, reducing synchronous load.
Data Structures
Orders are stored in a relational database to ensure ACID properties for transactions.
Example order schema:
{
"orderId": "o456",
"userId": "u456",
"items": [
{ "productId": "p123", "quantity": 2, "price": 50 },
{ "productId": "p987", "quantity": 1, "price": 30 }
],
"status": "CONFIRMED",
"totalAmount": 130,
"createdAt": "2025-06-07T15:30:00Z"
}
Two-phase commit (2PC) or saga pattern can be used for distributed transaction management, especially if payment and inventory updates are in separate systems.
Optimistic Locking: Prevent race conditions when updating the order status (like from PENDING to CONFIRMED).
Explain any trade offs you have made and why you made certain tech choices...
Trade-offs & Tech Choices
SQL (Postgres) for strong consistency in orders/payments, despite less flexibility than NoSQL.
Strong consistency for transactions, accepting slightly lower availability for reliability.
Caching with Redis for faster reads, even though it can serve stale data at times.
Partitioning initially for simpler scaling within a single DB, with sharding as future-proofing.
Asynchronous processing (message queues) for notifications/logistics, balancing latency with eventual consistency.
Microservices for scalability and easier feature updates, despite increased deployment complexity.
Load balancing for high availability, trading off cost and operational effort.
These choices ensure performance and reliability while planning for future scale.
Database Bottlenecks
Bottleneck:
Single primary DB instance can become a write bottleneck.
Read-heavy operations can overload primary.
Connection pool limits or unoptimized queries can degrade performance.
Solutions:
Use read replicas to handle read-heavy traffic (e.g., product catalog, search).
Use connection pooling (PgBouncer) to manage DB connections efficiently.
Optimize queries and indexes.
Scale vertically by using more powerful DB instance or scale horizontally with sharding if needed.
Implement partitioning on large tables (e.g., orders) to speed up reads.
Application Layer Bottlenecks
Bottleneck:
App threads can get blocked by slow DB or external API calls (e.g., payment gateway).
Thread pool exhaustion during bursts.
Solutions:
Use asynchronous, non-blocking frameworks (like Play, Akka in Scala) for concurrent handling.
Use circuit breakers and retries for external APIs to avoid thread blockage.
Separate critical paths (checkout) from less critical paths (search, browsing) with different thread pools.
Caching Layer Bottlenecks
Bottleneck:
Cache stampede on cache expiry can overload DB.
Cache down → DB read surge.
Solutions:
Cache warming: Preload popular data after cache expiry.
Use read-through or write-through cache strategies.
Use fallback reads from DB with proper throttling to avoid DB surge.
Run Redis in cluster mode for scalability and availability.
Load Balancer and Network
Bottleneck:
Single load balancer can become SPOF.
Network partition between load balancer and app servers.
Solutions:
Use multiple load balancers across availability zones.
Leverage managed load balancers (e.g., AWS ALB) with automatic scaling and health checks.
Deploy application servers in multiple zones to avoid partition issues.
Queueing and Asynchronous Work
Bottleneck:
Background job queues (e.g., for notifications, delivery partner assignment) can build up if consumers lag behind.
Solutions:
Use persistent queues (Kafka, RabbitMQ) with dead-letter queues for failed messages.
Autoscale queue consumers to keep up with message volume.
Monitor queue sizes and processing rates.
Scalability and Peak Handling
Bottleneck:
During peak hours (e.g., festival sales), sudden traffic surge can overwhelm services.
Solutions:
Autoscale application pods based on CPU/memory usage.
Use horizontal scaling (add more pods/servers) and vertical scaling (larger instances).
Use CDN for static content delivery to reduce backend load.
User-Facing Failures
Bottleneck:
Partial service failures can degrade UX.
Session loss if Redis down.
Solutions:
Graceful degradation: show partial data or fallback screens.
Session fallback to DB in case Redis cache fails.
Implement session replication or sticky sessions if needed.
Monitoring and Observability
Use Prometheus, Grafana, ELK stack for monitoring, alerting, and log analysis.
Set up health checks for all critical services and auto-heal pods if they fail.
What are some future improvements you would make? How would you mitigate the failure scenario(s) you described above?
Future Improvements
Geo-replication and Multi-region Deployment
Deploy in multiple cloud regions to reduce latency and improve disaster recovery.
Database Sharding
As the data size grows, shard by city or product category to distribute load.
Enhanced Observability
Add distributed tracing and more detailed metrics to identify bottlenecks quickly.
Dynamic Autoscaling
Use application-level autoscalers driven by real-time CPU and request load metrics.
Personalized User Experience
Implement recommendation systems or ML-based search for more relevant product discovery.
Event-driven Architecture
Use Kafka streams or similar to decouple services and improve real-time data handling.
Failure Mitigation Strategies
Database Failures
Use automated failover solutions like Patroni for Postgres to ensure quick recovery.
Cache Failures
Implement read fallback with DB throttling. Redis cluster mode ensures no single point of failure.
Application Crashes
Use horizontal scaling (more pods or servers) and auto-healing to recover quickly.
Queue Lag
Use dead-letter queues and autoscale queue consumers to handle spikes in background jobs.
Load Balancer or Network Failures
Deploy multiple load balancers across zones and use cloud-managed load balancers with failover.
Traffic Surges
Use CDNs for static content and autoscale app instances to manage peak demand.