Design Food Delivery Logistics & Dispatch Engine (DoorDash / Uber Eats)
Hyper-local three-sided marketplace coordinating customers, restaurants, and couriers with dynamic batching, kitchen prep estimation ML, and real-time bipartite dispatch matching.
1. System Requirements
- Coordinate real-time food delivery lifecycle: order placement, restaurant prep acceptance, courier dispatch, and drop-off.
- Estimate meal preparation times dynamically based on restaurant load and dish complexity.
- Intelligently match and bundle nearby orders to optimize courier delivery routes and reduce customer delivery fees.
- Sub-3 second dispatch match decision latency over rolling batch windows.
- High GPS telemetry ingestion handling 250,000 courier location updates per second.
- Accurate dynamic ETA predictions within a +/- 3 minute error margin.
2. Scale & Resource Estimates
Throughput: 5 Million orders per day. Peak load: 350 orders placed per second. 1 Million active couriers sending GPS pings every 3 seconds.
Storage & RAM: Active order state: 100,000 concurrent deliveries * 5 KB = 500 MB in Redis. Historical orders: 5M * 2 KB = 10 GB/day in PostgreSQL.
3. System API Endpoints
POST /api/v1/orders/place
Request:
{
"customer_id": "usr-1102",
"restaurant_id": "rst-chipotle-90",
"items": [{ "item_id": "burrito-bowl", "qty": 2 }],
"dropoff_location": { "lat": 40.7128, "lng": -74.0060 }
}
Response (201 Created):
{
"order_id": "ord-88129",
"estimated_delivery_time": "2026-10-22T18:42:00Z",
"status": "SENT_TO_KITCHEN"
}
POST /api/v1/courier/location
Request: { "courier_id": "cur-441", "lat": 40.7145, "lng": -74.0082, "status": "AVAILABLE" }
Response: { "ack": true }
4. Database Schema Design
Table: orders (PostgreSQL)
- order_id (UUID - Primary Key)
- customer_id (UUID)
- restaurant_id (UUID)
- courier_id (UUID - Nullable until matched)
- order_status (ENUM: CREATED, PREPARING, COURIER_ASSIGNED, PICKED_UP, DELIVERED)
- subtotal_cents (INTEGER)
- created_at (TIMESTAMP)
Table: courier_live_state (Redis Spatial H3)
- Key: courier:loc:{courier_id}
- Value: { lat, lng, speed, battery, active_orders_count }
Table: dispatch_matches (ClickHouse Analytics)
- match_id (UUID)
- order_id (UUID)
- courier_id (UUID)
- estimated_pickup_eta (TIMESTAMP)
- actual_pickup_time (TIMESTAMP)
5. Interactive High-Level Architecture
6. Scalability Deep Dive & Bottlenecks
Unlike ride-hailing (where drivers should arrive immediately), food delivery features a critical temporal decoupling between order placement and driver arrival. If the driver arrives too early, they waste 15 minutes waiting idle at the restaurant; if they arrive too late, hot food turns cold. The dispatch engine schedules a delayed matching trigger based on an ML-estimated kitchen prep time (e.g. 18 minutes). When the kitchen reaches 70% completion, a minimum-cost bipartite matching algorithm runs over 30-second batch intervals to assign the optimal driver.
7. Production Scaling Strategies
Temporal Dispatch Staging: Delay courier dispatch calculations until `prep_time_est - travel_time_est`. This reduces driver idle wait times at merchants by 45%, boosting courier hourly earnings.
Uber H3 Spatial Indexing: Partition geographic delivery areas into H3 hexagonal cells (resolution 8, ~460m radius). Hexagons offer uniform neighbor distances compared to square grids, accelerating proximity scans.
Order Bundling Optimizer: When two customers within 1 mile order from the same restaurant (or adjacent restaurants), bundle orders into a single multi-drop route for one courier, improving fleet delivery capacity by 30%.
Rolling 30-Second Match Windows: Avoid greedy first-come-first-served dispatch. Group pending orders and available drivers into 30-second batch windows, solving global assignment with Hungarian/Kuhn-Munkres algorithms.
Geofenced Arrival Triggers: Use mobile geofencing (50m radius around restaurant) to automatically transition order state to `COURIER_ARRIVED` via background OS triggers without manual driver taps.
Resilient Finite State Machine (FSM): Model the order lifecycle with strict FSM transitions. Invalid transition attempts (e.g. marking DELIVERED before PICKED_UP) are rejected at the database level with conditional constraints.
8. Runnable Production Starter Code
Scaffold