Requirements & Scope
Functional Requirements
- Browse restaurants: Users can view restaurants based on location, cuisine, ratings, delivery time
- View menus: Display restaurant menus with categories, items, prices, customization options
- Place orders: Add items to cart, apply coupons, select delivery address, choose payment method
- Track delivery: Real-time tracking of driver location and order status
- Rate restaurants/delivery: Post-delivery rating and review system
Non-Functional Requirements
- Real-time order tracking: Driver location updates every 5 seconds, sub-second latency to customer
- High availability: 99.99% uptime, failover across regions
- Consistent pricing: No price mismatches between menu and checkout
- Peak hour handling: Support 10x traffic during lunch/dinner rushes
- Low latency: API responses under 200ms for browsing, 500ms for order placement
Scale Estimation
| Metric | Daily | Per Second |
|---|---|---|
| Orders | 5M | ~60 |
| Restaurant updates | 100K | ~1 |
| Driver location updates | 500M | ~6K |
| Active drivers | 500K | - |
| API requests | 500M | ~6K |
Core Entities
- Restaurant, Menu, MenuItem, Order, OrderItem, Delivery, Driver, Payment, User, Address
Order Flow & State Machine
Order State Machine
PLACED → CONFIRMED → PREPARING → READY → PICKED_UP → DELIVERED → COMPLETED
↓ ↓ ↓ ↓ ↓ ↓ ↓
CANCELLED REJECTED CANCELLED - CANCELLED FAILED RATED
State Transitions
| From | To | Trigger | Actor |
|---|---|---|---|
| PLACED | CONFIRMED | Restaurant accepts order | Restaurant |
| PLACED | REJECTED | Restaurant rejects (capacity, items unavailable) | Restaurant |
| CONFIRMED | PREPARING | Restaurant starts preparing | Restaurant |
| PREPARING | READY | Food ready for pickup | Restaurant |
| READY | PICKED_UP | Driver picks up order | Driver |
| PICKED_UP | DELIVERED | Driver delivers to customer | Driver |
| DELIVERED | COMPLETED | Customer confirms delivery | System (auto 30min) |
| Any | CANCELLED | Customer/restaurant cancels | Customer/Restaurant |
Order Service Implementation
@Service
public class OrderService {
@Autowired private OrderRepository orderRepo;
@Autowired private PaymentService paymentService;
@Autowired private DeliveryService deliveryService;
@Autowired private NotificationService notificationService;
@Transactional
public Order placeOrder(CreateOrderRequest request) {
// 1. Validate restaurant is open
Restaurant restaurant = restaurantService.getRestaurant(request.getRestaurantId());
if (!restaurant.isOpen()) {
throw new RestaurantClosedException();
}
// 2. Validate menu items and prices
List<OrderItem> items = validateAndGetItems(request.getItems());
BigDecimal total = calculateTotal(items, request.getCouponCode());
// 3. Create order
Order order = Order.builder()
.userId(request.getUserId())
.restaurantId(request.getRestaurantId())
.items(items)
.totalAmount(total)
.deliveryAddress(request.getAddress())
.status(OrderStatus.PLACED)
.createdAt(Instant.now())
.build();
orderRepo.save(order);
// 4. Process payment (with idempotency key)
paymentService.charge(order.getId(), total, request.getPaymentMethod());
// 5. Notify restaurant
notificationService.notifyRestaurant(restaurant, order);
// 6. Start order expiry timer (15 min for restaurant to accept)
scheduler.schedule(() -> expireOrder(order.getId()), 15, TimeUnit.MINUTES);
return order;
}
public void updateOrderStatus(Long orderId, OrderStatus newStatus) {
Order order = orderRepo.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
validateStateTransition(order.getStatus(), newStatus);
order.setStatus(newStatus);
order.setUpdatedAt(Instant.now());
orderRepo.save(order);
// Trigger side effects based on status
switch (newStatus) {
case CONFIRMED:
deliveryService.findAndAssignDriver(order);
notificationService.notifyOrderConfirmed(order);
break;
case READY:
deliveryService.notifyDriverForPickup(order);
break;
case PICKED_UP:
notificationService.notifyOrderPickedUp(order);
break;
case DELIVERED:
paymentService.capturePayment(order.getId());
notificationService.notifyOrderDelivered(order);
break;
}
}
}
Order Data Model
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
restaurant_id BIGINT REFERENCES restaurants(id),
status ENUM('PLACED','CONFIRMED','PREPARING','READY',
'PICKED_UP','DELIVERED','COMPLETED','CANCELLED'),
total_amount DECIMAL(10,2),
delivery_address JSON,
special_instructions TEXT,
created_at TIMESTAMP,
updated_at TIMESTAMP,
estimated_delivery_time TIMESTAMP,
INDEX idx_user_orders (user_id, created_at),
INDEX idx_restaurant_orders (restaurant_id, status)
);
CREATE TABLE order_items (
id BIGINT PRIMARY KEY,
order_id BIGINT REFERENCES orders(id),
menu_item_id BIGINT REFERENCES menu_items(id),
quantity INT,
unit_price DECIMAL(10,2),
customizations JSON,
special_instructions TEXT
);
Delivery & Dispatch
Dispatch Algorithm
@Service
public class DispatchService {
private static final double MAX_ASSIGNMENT_RADIUS_KM = 5.0;
private static final int MAX_RETRIES = 3;
public DriverAssignment assignDriver(Order order) {
Restaurant restaurant = restaurantService.getRestaurant(order.getRestaurantId());
// Find nearby available drivers
List<Driver> candidates = driverService.findNearbyDrivers(
restaurant.getLocation(), MAX_ASSIGNMENT_RADIUS_KM);
// Score and rank drivers
List<DriverScore> scored = candidates.stream()
.map(driver -> new DriverScore(driver, calculateScore(driver, restaurant, order)))
.sorted(Comparator.comparingDouble(DriverScore::getScore).reversed())
.collect(Collectors.toList());
// Try assigning to top drivers
for (DriverScore driverScore : scored) {
if (tryAssign(driverScore.getDriver(), order)) {
return new DriverAssignment(driverScore.getDriver(), order);
}
}
// If no driver found, expand search or use surge pricing
return expandSearchAndAssign(order);
}
private double calculateScore(Driver driver, Restaurant restaurant, Order order) {
double distanceScore = 1.0 / (driver.getLocation()
.distanceTo(restaurant.getLocation()) + 0.1);
double ratingScore = driver.getRating() / 5.0;
double acceptanceScore = driver.getAcceptanceRate();
double experienceScore = driver.getTotalDeliveries() / 10000.0;
return (distanceScore * 0.4) + (ratingScore * 0.3) +
(acceptanceScore * 0.2) + (experienceScore * 0.1);
}
@Retryable(maxAttempts = MAX_RETRIES)
private boolean tryAssign(Driver driver, Order order) {
// Send assignment notification to driver
boolean accepted = notificationService.sendAssignment(driver, order,
Duration.ofSeconds(30));
if (!accepted) {
driver.incrementRejectionCount();
return false;
}
deliveryService.createDelivery(order, driver);
return true;
}
}
Real-Time Tracking
@Service
public class TrackingService {
@Autowired private WebSocketService webSocketService;
@Autowired private DriverLocationRepository locationRepo;
public void updateDriverLocation(String driverId, Location location) {
// Store location
locationRepo.save(DriverLocation.builder()
.driverId(driverId)
.location(location)
.timestamp(Instant.now())
.build());
// Find active delivery for this driver
Delivery delivery = deliveryService.getActiveDeliveryByDriver(driverId);
if (delivery != null) {
// Calculate ETA
int etaMinutes = calculateETA(location, delivery.getDeliveryAddress());
// Push update to customer via WebSocket
TrackingUpdate update = TrackingUpdate.builder()
.deliveryId(delivery.getId())
.driverLocation(location)
.etaMinutes(etaMinutes)
.build();
webSocketService.sendToCustomer(delivery.getOrderId(), update);
}
}
private int calculateETA(Location driverLocation, Address deliveryAddress) {
// Use Google Maps API or similar for ETA calculation
double distanceKm = driverLocation.distanceTo(deliveryAddress.getLocation());
// Assume average speed of 20 km/h in urban area
return (int) Math.ceil((distanceKm / 20.0) * 60);
}
}
Surge Pricing
- Demand detection: Monitor order volume vs available drivers per region
- Dynamic pricing: Increase delivery fee when driver-to-order ratio drops below threshold
- Driver incentives: Bonus per delivery during high-demand periods
- Customer communication: Show estimated delivery time and surge multiplier
Practice Problems
Design a scalable Food Delivery (Design DoorDash/UberEats) system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Food Delivery (Design DoorDash/UberEats) to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Food Delivery (Design DoorDash/UberEats) and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What is the recommended approach to handle restaurant order acceptance timeout?
2. How should driver location updates be handled for real-time tracking?
3. What happens when a driver rejects an order assignment?
4. How should menu prices be handled during peak hours?
5. What is the best approach for handling order state transitions?
Flashcards
Question
What is the order state machine for food delivery?
Click to reveal answer
Answer
PLACED → CONFIRMED → PREPARING → READY → PICKED_UP → DELIVERED → COMPLETED. Each transition has specific triggers and actors.
Question
How does the dispatch algorithm assign drivers?
Click to reveal answer
Answer
Scores drivers based on distance (40%), rating (30%), acceptance rate (20%), and experience (10%). Tries top-scored drivers first with retry logic.
Question
What protocol should be used for real-time order tracking?
Click to reveal answer
Answer
WebSocket for push-based updates from server to client. Driver location stored in Redis, broadcast to customers via WebSocket channels.
Question
How should menu data be cached?
Click to reveal answer
Answer
Cache menu in Redis with 5-minute TTL. Invalidate cache when restaurant updates menu. Use cache-aside pattern for menu reads.
Question
What is surge pricing and when is it triggered?
Click to reveal answer
Answer
Dynamic delivery fee increase when driver-to-order ratio drops below threshold in a region. Incentivizes more drivers and manages demand.
Question
How do you handle order timeout?
Click to reveal answer
Answer
Set 15-minute timer when order is PLACED. If restaurant doesn't accept within timeout, auto-cancel order and refund payment.
Question
What data model is needed for food delivery?
Click to reveal answer
Answer
Restaurants, MenuItems, Orders, OrderItems, Deliveries, Drivers, DriverLocations, Payments, Users, Addresses.
Revision Notes
Key Takeaways
- 1.Order state machine is critical for managing order lifecycle
- 2.Real-time tracking requires WebSocket and efficient location storage
- 3.Dispatch algorithm must balance distance, driver quality, and availability
- 4.Surge pricing helps balance supply and demand during peak hours
- 5.Menu caching reduces database load for frequent read operations
- 6.Idempotency is essential for payment and order operations
Interview Tips
- •Start with functional requirements and scale estimation
- •Draw the high-level architecture with all services before diving deep
- •Explain the order state machine clearly with all transitions
- •Discuss real-time tracking approach (WebSocket vs polling)
- •Explain the dispatch algorithm and how it handles driver rejection
- •Mention how you handle peak hours and surge pricing
- •Be ready to discuss database schema and indexing strategy
Cheat Sheet
Food Delivery System - Key Points
Architecture Components
- Restaurant Service: Menu management, availability, pricing
- Order Service: Order lifecycle, state machine
- Delivery Service: Driver dispatch, tracking
- Payment Service: Charges, refunds, idempotency
- Notification Service: Push notifications, SMS, email
Order State Machine
PLACED → CONFIRMED → PREPARING → READY → PICKED_UP → DELIVERED → COMPLETED
Key Design Decisions
- Real-time tracking: WebSocket + Redis for location storage
- Dispatch: Score-based algorithm (distance, rating, acceptance)
- Caching: Menu cached in Redis (5min TTL)
- Scaling: Database sharding by region, queue for order processing
- Reliability: Idempotent operations, retry logic, timeouts
Scale Numbers
- 5M daily orders (~60/sec)
- 500K active drivers
- 6K location updates/second
- 500M API requests/day