Design a Food Delivery Service like DoorDash
Problem
Design a three-sided food delivery marketplace. Customers browse restaurants that deliver to their address, build a cart and place an order, and track it live until it arrives. Restaurants receive and confirm orders on a tablet and mark them ready. The platform assigns a courier who picks up the food and delivers it, timing the pickup to match when the food is ready. The service handles 10 million orders per day across 500,000 restaurants and 1 million active couriers.
- How is the delivery radius for a restaurant defined: distance, drive time, or drawn zones?
- Can a courier carry several orders at once (batching)?
- How accurate must the delivery time estimate shown before ordering be?
- Do restaurants manage their own menus and item availability in real time?
- How are payments split between restaurant, courier, and platform?
- What happens if no courier accepts an order for several minutes?
Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.
- Show restaurants that deliver to the customer's address, with menus and estimated delivery times.
- Place an order with payment, and notify the restaurant to accept it.
- Assign a courier and time the pickup to the restaurant's preparation time.
- Show live order status and courier location to the customer.
- Let restaurants mark items as unavailable and pause ordering when busy.
- Batch two orders onto one courier when pickups and drop-offs are close.
- Handle cancellations and refunds at each stage.
Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.
- 10 million orders per day, about 115 per second average and around 1,000 per second at the dinner peak.
- 1 million couriers online at peak sending location every 5 seconds: about 200,000 updates per second.
- Restaurant browse traffic is about 50 times order traffic, around 50,000 requests per second at peak.
- An order must never be charged twice or lost after payment; order state must be strongly consistent.
- Courier assignment within 1 minute for most orders.
- Regional isolation, so an issue in one market does not take down others.
Discovery is served from a search index of restaurants keyed by delivery zone, with cached menus and a prediction service for delivery times. Orders are run by an order service whose state machine coordinates payment, restaurant confirmation, courier dispatch, and delivery through durable events, so each step can retry independently. A dispatch service continuously matches ready-soon orders with couriers using a geospatial index of courier locations and predicted preparation times.
Loading diagram...
Browse. The customer's address is converted to a delivery zone and coordinates. The discovery service queries the search index for open restaurants whose delivery polygon contains that point, ranks them, and attaches a delivery estimate from the ETA service. Menus are fetched from the menu service, which is cached aggressively.
Place an order. The client sends the cart with an idempotency key. The order service re-prices the cart from the menu service, creates the order in CREATED, and asks the payment service to authorize the card. On success it moves to SENT_TO_RESTAURANT and pushes the order to the restaurant tablet. When the restaurant confirms with a prep time estimate, the order becomes CONFIRMED and an event is published.
Dispatch. The dispatch service receives the confirmed order and the predicted ready time. It waits until the courier travel time to the restaurant roughly equals the remaining prep time, then searches the courier geo index for available couriers nearby, scores them, and sends an offer. On acceptance the order moves to COURIER_ASSIGNED.
Deliver. The courier marks pickup (PICKED_UP) and drop-off (DELIVERED). Location updates are streamed to the customer's tracking screen. On delivery the order service captures the payment, and payouts to the restaurant and courier are recorded.
Owns the order state machine and coordinates every other service.
- A sharded relational database (PostgreSQL by region) for orders, and Kafka for order events using the transactional outbox pattern.
- A workflow engine such as Temporal for timeouts and retries.
Diagram
Loading diagram...
API
POST /orderswith{ restaurantId, items, addressId, paymentMethodId, idempotencyKey }returns{ orderId, status, estimatedDeliveryAt }.GET /orders/:idreturns status and courier location.POST /orders/:id/cancel.
Data Model
orders(order_id, customer_id, restaurant_id, courier_id, status, subtotal, fees, total, ready_at_estimate, created_at, version)order_items(order_id, item_id, quantity, unit_price, options_json)outbox(event_id, order_id, event_type, payload, published)
Scalability
About 1,000 order writes per second at peak, each with a handful of state updates, is comfortable for a regionally sharded database. Orders are sharded by region so each market is isolated.
Tradeoffs
Writing the state change and the outgoing event in one transaction (outbox) guarantees no event is lost or emitted for a rolled-back change. Publishing to Kafka directly after the commit risks losing events when the service crashes between the two writes.
Assigns couriers to orders, timed so food does not sit waiting and couriers do not wait at the counter.
- Stateful service sharded by market, with an in-memory pool of pending orders and available couriers.
Scalability
Each market runs a matching cycle every few seconds over its pending orders, solving a small assignment problem (couriers to orders, with possible two-order batches) that minimizes a weighted sum of courier wait, food wait, and lateness. Large cities are split into subregions to keep each problem small.
Tradeoffs
Delaying dispatch until close to the predicted ready time reduces courier idle time at restaurants, which is the largest waste in the system. The risk is late food when the courier pool is thin, so dispatch starts earlier when supply in the area is low.
Tracks where every online courier is.
- Realtime gateway for courier connections; Redis with geospatial sets or an H3 cell index, sharded by market.
Data Model
courier_state(courier_id) -> { lat, lng, status, active_order_ids, vehicle, updated_at }
Scalability
200,000 updates per second are in-memory writes spread across market shards. Updates for couriers on an active delivery are also forwarded to the subscribed customer connection and sampled into a trip history for disputes.
Tradeoffs
Only the latest position is kept in the hot index; history is written in compressed batches. Persisting every point synchronously would cost far more than the value of that data.
Shows which restaurants can deliver to an address and what they sell.
- Elasticsearch with
geo_shapedelivery polygons for discovery; a document store for menus with a CDN-backed cache.
API
GET /restaurants?lat=&lng=&cuisine=&q=returns restaurants with estimated delivery time.GET /restaurants/:id/menureturns the menu with item availability.PATCH /restaurants/:id/items/:itemIdlets the restaurant mark an item unavailable.
Scalability
Browse traffic is served mostly from cache. Item availability changes are pushed as small updates that invalidate only the affected menu.
Tradeoffs
Menus are cached for a minute, so a customer can occasionally add an item that just sold out. The order service re-validates every item at checkout, which is cheaper than making every menu read strongly consistent.
Predicts prep time, courier travel time, and total delivery time.
- Gradient boosted models trained on historical orders, served online with features such as restaurant load, time of day, and weather.
Scalability
Discovery requests ask for estimates for dozens of restaurants at once, so predictions are batched and cached per restaurant and zone for a minute.
Tradeoffs
Learned prep time per restaurant and hour is far more accurate than relying on the restaurant's own estimate, which is usually optimistic. The model is retrained daily so it adapts to changes in kitchen speed.
Authorizes, captures, refunds, and splits payments.
- An external processor for card payments; an internal double-entry ledger for restaurant and courier payouts.
Scalability
Authorization is synchronous at checkout; capture and payout recording run asynchronously from order events with idempotency keys derived from the order id.
Tradeoffs
Authorizing at checkout and capturing at delivery means the customer is only charged for orders that arrive, simplifying refunds for cancellations. The cost is that some authorizations expire on very delayed orders and must be retried.