Design a Food Delivery Service
0%
Editorial Solution
System requirements
Functional
Capacity estimation
Traffic
Storage
API design
Order APIs
Database design
Schema
Storage Engine Choices
High-level design
Components
Request flows
Order Placement Flow
Detailed component design
Order State Machine: The Core Challenge
Trade-offs and tech choices
SQL vs NoSQL for Different Workloads
Failure scenarios and bottlenecks
Payment Service Failure During Order Placement
Future improvements
ML-Driven Delivery Time Prediction
Route Optimization for Multi-Order Drivers
Table of Contents
System requirements
Functional
Capacity estimation
Traffic
Storage
API design
Order APIs
Database design
Schema
Storage Engine Choices
High-level design
Components
Request flows
Order Placement Flow
Detailed component design
Order State Machine: The Core Challenge
Trade-offs and tech choices
SQL vs NoSQL for Different Workloads
Failure scenarios and bottlenecks
Payment Service Failure During Order Placement
Future improvements
ML-Driven Delivery Time Prediction
Route Optimization for Multi-Order Drivers
Functional
A food delivery service connects hungry users, restaurants, and delivery drivers through a real-time marketplace. Think DoorDash, Uber Eats, or Grubhub. The platform coordinates three distinct user types with very different needs: customers ordering food, restaurants preparing it, and drivers delivering it.
- Order placement: Users browse nearby restaurants, view menus with photos and prices, add items to a cart with customization options (extra sauce, no onions, spice level), and checkout. The system validates item availability against the restaurant's current menu before processing payment. Support for scheduled orders (order now, deliver at 7 PM).
- Real-time order tracking: After placing an order, the user sees live status updates: confirmed → preparing → ready for pickup → driver en route → delivered. A map shows the driver's live GPS position updating every few seconds. Estimated delivery time updates dynamically based on driver location and traffic.
- Restaurant management: Restaurants register on the platform, configure operating hours, manage their menu (add/remove/update items, mark items as sold out), and receive incoming orders. The restaurant dashboard shows pending orders with a countdown timer and an "accept/reject" button. Menu changes propagate to users immediately.
- Driver matching and dispatch: When an order is ready for pickup, the system matches it with the nearest available driver. Drivers see available delivery offers with pickup location, drop-off location, estimated pay, and distance. A driver accepts or declines within 30 seconds; on timeout, the offer goes to the next nearest driver.
- Payment processing: Supports credit/debit cards, digital wallets, and cash on delivery. Payment is authorized (hold) at order placement and captured after delivery confirmation. Refunds for cancelled orders or quality issues. Tips added post-delivery.
- Ratings and reviews: Users rate restaurants (1-5 stars) and drivers (thumbs up/down) after delivery. Restaurants rate drivers. These ratings affect search ranking and driver priority in future matching.
Traffic
- Daily orders: 10M orders/day. 70% during 4 peak hours = 7M orders in 14,400 seconds = ~486 orders/sec at peak. Super Bowl spike at 5x = ~2,400 orders/sec.
- API throughput: Each order generates ~15 API calls (browse restaurants, view menu, add to cart, checkout, payment, tracking updates, driver matching, status updates, rating). 10M orders × 15 = 150M API calls/day. Peak: ~10K API requests/sec.
- Driver location updates: 200K active drivers × 1 GPS update every 5 seconds = 40K location writes/sec. Each update is small (driver_id, lat, lng, timestamp) ≈ 100 bytes. This is the highest write throughput in the system.
- WebSocket connections: 500K concurrent orders × 1 user tracking each = 500K concurrent WebSocket connections for live tracking.
Storage
Order APIs
POST /v1/orders
1{
2 "restaurantId": "rest_abc",
3 "items": [
4 {"menuItemId": "item_1", "quantity": 2, "customizations": ["extra cheese"]},
5 {"menuItemId": "item_2", "quantity": 1, "customizations": []}
6 ],
7 "deliveryAddress": {"lat": 37.7749, "lng": -122.4194, "formatted": "123 Main St"},
8 "paymentMethodId": "pm_xyz",
9 "idempotencyKey": "ord_client_abc123",
10 "scheduledFor": null,
11 "tip": 500
12}Schema
| Table | Key Columns | Notes |
| users | user_id (PK), email (UNIQUE), name, phone, default_address (JSONB) | Customer accounts |
| restaurants | restaurant_id (PK), name, location (POINT), cuisine_type, rating, operating_hours (JSONB), is_active | PostGIS for geo queries |
| menu_items | item_id (PK), restaurant_id (FK), name, price_cents, category, is_available, customizations (JSONB) | Per-restaurant menus |
| orders | order_id (PK), user_id (FK), restaurant_id (FK), driver_id (FK), status, items (JSONB), total_cents, delivery_address (JSONB), idempotency_key (UNIQUE) | Items stored as point-in-time snapshot; idempotency_key prevents duplicate orders |
| order_status_history | id (PK), order_id (FK), status, changed_by, changed_at | Audit trail, who changed status and when |
| drivers | driver_id (PK), name, phone, vehicle_type, is_available, rating | Current location stored in Redis, not here |
| payments | payment_id (PK), order_id (FK), amount_cents, method, status, provider_txn_id | Status: authorized → captured → refunded |
Storage Engine Choices
Components
Client apps (user, restaurant, driver): Three distinct apps. The user app handles browsing, ordering, and tracking. The restaurant app (tablet-based) shows incoming orders with accept/reject buttons and a prep timer. The driver app shows delivery offers, navigation, and a "mark as delivered" button. All three connect through the same API Gateway but access different endpoints.
Order Placement Flow
- User taps "Place Order" in the app. Client sends
POST /v1/orderswith cart items, delivery address, payment method, and idempotency key. - API Gateway validates JWT, rate-checks, and routes to Order Service.
- Order Service checks idempotency key in Redis. If exists, returns the existing order (duplicate request). If new, proceeds.
- Order Service validates: restaurant is open, all items are available (query menu_items), delivery address is within the restaurant's delivery radius. Returns 400 with specific error if any validation fails.
- Order Service calls Payment Service: authorize (hold) the order total on the user's card. Payment Service calls Stripe, receives authorization. Returns payment_id.
- Order Service writes to PostgreSQL in a transaction: INSERT into orders (status = "placed"), INSERT into order_status_history, INSERT into payments (status = "authorized"). SET idempotency key in Redis with 24-hour TTL.
- Order Service publishes
order_placedevent to Kafka. - Returns 201 to the user with order_id, estimated delivery time, and total. The user does not wait for restaurant acceptance, everything below happens asynchronously.
- Restaurant Service consumes the Kafka event, pushes the order to the restaurant app via push notification and WebSocket.
- Restaurant accepts the order → publishes
order_confirmedevent. Order Service updates status to "confirmed." If the restaurant rejects or doesn't respond within 5 minutes, the order is auto-cancelled and the payment hold is released.
Order State Machine: The Core Challenge
This is the heart of the food delivery system. Every order moves through a finite set of states with strictly enforced transitions. Invalid transitions are rejected, preventing corrupted order states.
SQL vs NoSQL for Different Workloads
PostgreSQL for transactional data (orders, payments): ACID compliance is non-negotiable for financial transactions. An order placement involves coordinated writes across orders, order_status_history, and payments, a single transaction guarantees all-or-nothing. PostGIS provides native geospatial queries for restaurant search. The downside: PostgreSQL's write throughput is limited (~10-50K writes/sec per primary, depending on hardware). For 486 orders/sec at peak, this is fine. For 40K location writes/sec; it would be a bottleneck.
Redis for real-time location data: In-memory storage handles 40K writes/sec easily (Redis benchmarks at 100K+ ops/sec). Geospatial commands (GEORADIUS) are purpose-built for the driver matching use case. Trade-off: Redis is volatile, if it crashes, all current driver locations are lost. Mitigation: drivers re-report within 5 seconds, so the data self-heals in seconds. Redis persistence (RDB snapshots) adds a safety net but isn't critical for location data.
Payment Service Failure During Order Placement
User places an order. Order Service calls Payment Service to authorize the card. Payment Service calls Stripe but gets a timeout. What happens?
- Retry with idempotency key: Payment Service retries the Stripe call with the same idempotency key. Stripe's idempotency ensures the charge is not duplicated even if the first call actually succeeded but the response was lost. Retry up to 3 times with exponential backoff.
- Circuit breaker: If Payment Service error rate exceeds 50% over the last 100 calls, the circuit opens. Subsequent order placements get a "payment temporarily unavailable" error immediately instead of waiting for timeouts. The user sees "Please try again in a few minutes."
- Compensation for captured-but-lost orders: Worst case: Stripe successfully authorized, but the Order Service crashed before saving the order. On recovery, a reconciliation job compares Stripe authorizations (via Stripe's list API) with the orders table. Orphaned authorizations are voided. This runs every 15 minutes.
ML-Driven Delivery Time Prediction
Current ETA is distance/speed + restaurant average prep time, a crude estimate. An ML model can incorporate: historical prep times for the specific restaurant and dish type, current restaurant order volume (10 pending orders = longer prep), traffic conditions at this time of day, weather (rain slows delivery 15-20%), and driver historical delivery speed. Training data: millions of past orders with actual timestamps for each state transition. The model predicts each stage independently: prep time, driver pickup time, delivery time. Combined ETA is more accurate than a static formula and adapts to conditions in real time.
Route Optimization for Multi-Order Drivers
Try this problem yourself
Reading a solution is not the same as producing one under interview pressure. Design it on the whiteboard and get scored feedback.
0%
Editorial Solution
System requirements
Functional
Capacity estimation
Traffic
Storage
API design
Order APIs
Database design
Schema
Storage Engine Choices
High-level design
Components
Request flows
Order Placement Flow
Detailed component design
Order State Machine: The Core Challenge
Trade-offs and tech choices
SQL vs NoSQL for Different Workloads
Failure scenarios and bottlenecks
Payment Service Failure During Order Placement
Future improvements
ML-Driven Delivery Time Prediction
Route Optimization for Multi-Order Drivers
Table of Contents
System requirements
Functional
Capacity estimation
Traffic
Storage
API design
Order APIs
Database design
Schema
Storage Engine Choices
High-level design
Components
Request flows
Order Placement Flow
Detailed component design
Order State Machine: The Core Challenge
Trade-offs and tech choices
SQL vs NoSQL for Different Workloads
Failure scenarios and bottlenecks
Payment Service Failure During Order Placement
Future improvements
ML-Driven Delivery Time Prediction
Route Optimization for Multi-Order Drivers