Food delivery is a three-sided marketplace connecting customers, restaurants, and delivery drivers — more complex than ride-sharing because orders involve a waiting period at the restaurant, menu management, and payment split across three parties. DoorDash, Uber Eats, and Grubhub face unique challenges around order batching, restaurant integration, and dynamic delivery time estimation.
Three-Sided Marketplace
Unlike ride-sharing (two parties), food delivery coordinates three independent parties with different needs:
| Party | Needs | Key Data |
|---|---|---|
| Customer | Browse menus, place orders, track delivery, rate experience | Location, payment method, dietary preferences, order history |
| Restaurant | Receive and manage orders, update menu availability, set prep times | Menu items, prep time per item, operating hours, busy status |
| Dasher (Driver) | Accept orders, navigate pickup and delivery, maximize earnings | Current location, vehicle type, acceptance rate, active deliveries |
Functional Requirements
- Customer: browse restaurant menus, place order, pay, track delivery in real-time
- Restaurant: receive order notifications, manage order flow (received → preparing → ready), update menu items
- Driver: receive delivery assignment, navigate to restaurant and customer, mark delivery complete
- Order management: create, track, cancel, refund
- Real-time ETA for both restaurant pickup and customer delivery
High-Level Architecture
[Customer App] [Restaurant App] [Driver App]
↓ ↓ ↓
[API Gateway + Auth]
/ | |
[Order Service] [Menu Service] [Restaurant [Dispatch
Service] Service]
| | | |
[Order DB] [Menu Cache] [Restaurant DB] [Driver
(Postgres) (Redis) (Postgres) Location]
| (Redis Geo)
[Payment Service]
|
[Stripe / Payment Gateway]
↓
[Notification Service] → Customer, Restaurant, Driver push/SMS
Order State Machine
Customer places order
↓
CREATED
↓ [Payment authorized]
PAID
↓ [Sent to restaurant]
SENT_TO_RESTAURANT
↓ [Restaurant accepts]
CONFIRMED
↓ [Restaurant starts cooking]
PREPARING
↓ [Driver assigned]
DRIVER_ASSIGNED
↓ [Driver arrives at restaurant]
DRIVER_AT_RESTAURANT
↓ [Restaurant marks order ready, driver picks up]
PICKED_UP
↓ [Driver arrives at customer]
DELIVERED
↓ [Customer rates, payment settled]
COMPLETED
Side transitions:
CREATED → CANCELLED (customer cancels before payment)
PAID → REFUNDED (restaurant rejects or is closed)
PREPARING → CANCELLED_LATE (restaurant cannot fulfill — compensation to customer)
// Order state transitions in code
class OrderService:
def confirm_order(self, order_id: str, restaurant_id: str):
order = db.get_order(order_id)
assert order.status == OrderStatus.SENT_TO_RESTAURANT
assert order.restaurant_id == restaurant_id
order.status = OrderStatus.CONFIRMED
order.confirmed_at = datetime.now()
# Estimate prep time and start driver search
prep_time = estimate_prep_time(order)
dispatch_service.find_driver(order, available_in=prep_time)
db.save(order)
notifications.notify_customer(order, "Your order is confirmed!")
Menu Management
-- Menu schema
CREATE TABLE restaurants (id, name, address, lat, lon, operating_hours, avg_rating);
CREATE TABLE menu_categories (id, restaurant_id, name, display_order);
CREATE TABLE menu_items (
id BIGINT PRIMARY KEY,
category_id BIGINT REFERENCES menu_categories,
name VARCHAR(255) NOT NULL,
description TEXT,
price_cents INT NOT NULL,
is_available BOOLEAN DEFAULT TRUE,
prep_time_minutes INT
);
CREATE TABLE item_options (id, item_id, option_name, choices JSON, required BOOLEAN);
-- Redis cache for active restaurant menus (TTL 5 minutes)
-- Key: menu:{restaurant_id}
-- Value: full serialized menu JSON
-- Invalidated when restaurant updates any item
-- Hot path: 95% of menu views served from Redis
-- Cold path: cache miss → PostgreSQL → rebuild cache
Driver Dispatch and Order Batching
Basic Dispatch
Similar to ride-sharing: find available drivers near the restaurant using Redis GEORADIUS, estimate pickup time, offer to best candidate with timeout.
Order Batching (Stacked Orders)
DoorDash and Uber Eats allow drivers to pick up orders from multiple restaurants in one trip. The algorithm evaluates whether adding a new order to an in-progress delivery increases total earnings relative to total added time:
def should_batch(driver, new_order, active_delivery):
# Driver is heading to/from restaurant A
# Evaluate adding restaurant B and customer B to the route
# Extra distance for batched route vs direct route
current_route_time = route_time(driver.pos, active_delivery.restaurant,
active_delivery.customer)
batched_route_time = optimized_route_time([
driver.pos,
active_delivery.restaurant,
new_order.restaurant,
active_delivery.customer,
new_order.customer,
])
added_time = batched_route_time - current_route_time
# Batch if: added time 600: # more than 10 minutes added
return False
new_eta = current_time() + batched_route_time
if new_eta > new_order.promised_delivery_time:
return False
return True
Real-Time ETA Computation
Delivery ETA = restaurant prep time + driver travel to restaurant + driver travel to customer.
def estimate_delivery_eta(order, driver_assigned=None) -> datetime:
restaurant = get_restaurant(order.restaurant_id)
# Prep time: ML model trained on restaurant + item + current busy-ness
prep_time = ml_prep_estimator.predict(
restaurant_id=order.restaurant_id,
items=order.items,
current_order_queue_length=restaurant.active_order_count,
time_of_day=datetime.now().hour,
)
if driver_assigned:
# Time for driver to reach restaurant
driver_to_restaurant = route_eta(driver_assigned.position,
restaurant.location)
# Total wait = max(prep done time, driver arrival time)
pickup_time = max(
datetime.now() + timedelta(seconds=prep_time),
datetime.now() + timedelta(seconds=driver_to_restaurant),
)
else:
# No driver yet — estimate when one will be available
pickup_time = datetime.now() + timedelta(seconds=prep_time + 300)
# Restaurant to customer
restaurant_to_customer = route_eta(restaurant.location, order.delivery_address)
return pickup_time + timedelta(seconds=restaurant_to_customer)
Payment and Settlement
Food delivery payments are complex — money flows between four parties:
- Customer pays: full amount (food + delivery fee + tip + service fee) → DoorDash
- DoorDash pays restaurant: food subtotal minus platform commission (15-30%)
- DoorDash pays driver: delivery fee + tip + any promotions (weekly settlement)
- DoorDash keeps: service fee + platform commission
Use Stripe Connect or similar for marketplace payments with automatic split and payout. All settlements use an event-driven ledger for auditability.
Interview Discussion Points
- How do you handle restaurant order rejection? Auto-refund with coupon, offer customer alternative restaurants, penalize restaurant for frequent rejections
- How do you estimate restaurant prep time accurately? ML model: historical prep time per restaurant, item complexity, queue length at order time, day/time features. Real-time updates from restaurant app as they start and complete orders
- How do you prevent driver fraud (marking delivered without delivering)? Geofencing: delivery must be confirmed within 100m of customer address; photo proof upload; customer feedback loop
- How do you handle surge demand during lunch rush? Pre-scale driver supply with guaranteed earnings promotions in advance; adjust restaurant estimated prep times based on queue length; show longer ETA quotes to manage customer expectations
Frequently Asked Questions
How does a food delivery app coordinate between three parties?
A food delivery platform is a three-sided marketplace: customer, restaurant, and driver each have separate apps and distinct needs. The coordination flow: (1) Customer places an order; payment is authorized immediately. (2) Order is sent to the restaurant app and enters SENT_TO_RESTAURANT state. (3) Restaurant confirms and begins preparation; this triggers the dispatch service to find an available driver estimated to arrive when food is ready. (4) Driver accepts, picks up food, and navigates to customer. (5) Delivery is confirmed; payment is settled and split: restaurant receives food subtotal minus platform commission, driver receives delivery fee plus tip, platform keeps the service fee. Each state transition fires notifications to all relevant parties.
How does a food delivery system estimate delivery time?
Delivery ETA has three components: restaurant prep time, driver travel to restaurant, and driver travel from restaurant to customer. Restaurant prep time is estimated by an ML model trained on historical data, using features like restaurant ID, ordered items, current queue length, and time of day. Driver travel times use real-time road network routing (Google Maps API or an internal routing engine) with live traffic data. The total ETA is: max(prep done time, driver arrival time) + restaurant-to-customer travel time. The ETA is continuously updated as the order progresses — when a driver accepts, the ETA becomes more accurate; when food is ready and driver picks up, the final delivery ETA becomes precise.
How does order batching (stacked orders) work in DoorDash?
Order batching lets a single driver pick up from multiple restaurants in one trip, increasing driver earnings and platform efficiency. The algorithm evaluates whether adding a new pickup to an in-progress delivery is worth the added time: it compares the total route time of the combined delivery (both pickups and both drop-offs in optimized order) against the original single delivery route. If the added time is under a threshold (typically 8-10 minutes) AND both customers still receive delivery within their promised windows, the system offers the second order to the driver. The batching decision is made automatically before the driver is offered the additional order, so drivers see a combined offer with both pickup addresses and estimated earnings.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How does a food delivery app coordinate between three parties?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “A food delivery platform is a three-sided marketplace: customer, restaurant, and driver each have separate apps and distinct needs. The coordination flow: (1) Customer places an order; payment is authorized immediately. (2) Order is sent to the restaurant app and enters SENT_TO_RESTAURANT state. (3) Restaurant confirms and begins preparation; this triggers the dispatch service to find an available driver estimated to arrive when food is ready. (4) Driver accepts, picks up food, and navigates to customer. (5) Delivery is confirmed; payment is settled and split: restaurant receives food subtotal minus platform commission, driver receives delivery fee plus tip, platform keeps the service fee. Each state transition fires notifications to all relevant parties.”
}
},
{
“@type”: “Question”,
“name”: “How does a food delivery system estimate delivery time?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Delivery ETA has three components: restaurant prep time, driver travel to restaurant, and driver travel from restaurant to customer. Restaurant prep time is estimated by an ML model trained on historical data, using features like restaurant ID, ordered items, current queue length, and time of day. Driver travel times use real-time road network routing (Google Maps API or an internal routing engine) with live traffic data. The total ETA is: max(prep done time, driver arrival time) + restaurant-to-customer travel time. The ETA is continuously updated as the order progresses — when a driver accepts, the ETA becomes more accurate; when food is ready and driver picks up, the final delivery ETA becomes precise.”
}
},
{
“@type”: “Question”,
“name”: “How does order batching (stacked orders) work in DoorDash?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Order batching lets a single driver pick up from multiple restaurants in one trip, increasing driver earnings and platform efficiency. The algorithm evaluates whether adding a new pickup to an in-progress delivery is worth the added time: it compares the total route time of the combined delivery (both pickups and both drop-offs in optimized order) against the original single delivery route. If the added time is under a threshold (typically 8-10 minutes) AND both customers still receive delivery within their promised windows, the system offers the second order to the driver. The batching decision is made automatically before the driver is offered the additional order, so drivers see a combined offer with both pickup addresses and estimated earnings.”
}
}
]
}