Design a Food Delivery App (DoorDash)
How to design DoorDash / Uber Eats for interviews: restaurant catalog, cart and checkout, courier dispatch, ETA, live tracking, and peak-hour scaling.
Food delivery sits between Uber ride hailing and e-commerce. You still match supply and demand in space and time, but the “trip” includes a restaurant prep constraint, a multi-stop courier sometimes, and a cart/checkout that looks like shopping. Interviewers listen for dispatch + ETA + order state machine — not a generic microservice collage.
Scope via the framework: one metro, customers/restaurants/couriers, place order → deliver. Defer grocery, group orders, and ads. Payments can be a black box pointing at the payment article.
Functional requirements
- Browse nearby restaurants and menus; search dishes.
- Cart, coupons, checkout; place order.
- Restaurant accepts/rejects; updates prep status.
- Dispatch courier; live track delivery.
- Ratings, order history, basic support hooks.
- Optional: batching two orders to one courier (mention as optimization).
Non-functional
- Order placement P99 under a second.
- Location updates every few seconds during active delivery.
- Correctness on order state — no double charge, no lost orders.
- Lunch/dinner peaks 5–10× baseline; degrade discovery before breaking checkout.
- Geo accuracy good enough for ETA, not military GPS.
Three-sided marketplace
Optimize for customer ETA, restaurant idle time, and courier utilization together. A design that only minimizes customer wait will starve restaurants or burn couriers — say that out loud.
Capacity sketch
City-scale: 50K active customers at dinner, 5K restaurants, 3K couriers online. Order rate ~200/sec peak in one region. Courier location updates at 0.2–1 Hz → few thousand points/sec — fine for Kafka + spatial index, similar order of magnitude to Uber’s location stream but with stickier “assignments.”
High-level architecture
- Catalog / discovery — restaurants, menus, hours; geo query (Yelp-like).
- Cart & order service — cart lines, pricing snapshot, order state machine.
- Payment service — authorize on place, capture on handoff/delivery.
- Restaurant device API — accept, mark ready.
- Dispatch service — match order to courier with constraints.
- Location & ETA — courier GPS stream, traffic-aware ETA (maps ideas).
- Tracking — WebSocket/push to customer app.
- Notifications — SMS/push on status changes.
Order state machine
- CREATED → PAYMENT_AUTHORIZED → SENT_TO_RESTAURANT.
- ACCEPTED → PREPARING → READY_FOR_PICKUP.
- COURIER_ASSIGNED → PICKED_UP → DELIVERED.
- Terminal: CANCELED / FAILED with refunds rules.
Persist every transition with timestamps. Couriers and restaurants should not invent states client-side — servers own the machine. Idempotent status webhooks prevent double transitions when mobile networks flake.
Data model
| Data | Store | Notes |
|---|---|---|
| Restaurant / menu | SQL | Versioned prices; snapshot into order lines |
| Orders | SQL shard by city or order_id | State + totals; strong writes |
| Courier session | Redis + SQL | Online flag, current order_id |
| Location points | Redis geo / Kafka | Hot ephemeral; sample to warehouse |
| Ratings | SQL | Async, not on critical path |
Discovery and cart
Geohash or quadtree for “open restaurants near me,” then rank by ETA, rating, and promo. Cache menu reads aggressively (caching); invalidate on restaurant edits. Cart holds price snapshots so a menu change mid-checkout does not silently change the charged amount — same lesson as ticket holds.
Dispatch
- When order is ACCEPTED (or earlier, predictively), build a courier candidate set in a radius.
- Score by distance to restaurant, active load, vehicle type, batching potential.
- Offer to top courier with a short timeout; on decline/timeout, try next (or broadcast with fencing).
- On accept, lock assignment with a distributed lock or conditional update so two orders do not claim the same courier slot incorrectly.
Unlike Uber’s continuous matching, food dispatch often waits until prep is underway so couriers do not idle at the door. Mention predictive dispatch as a stretch: start the courier just in time for READY_FOR_PICKUP.
ETA and tracking
ETA = restaurant prep estimate + courier travel to restaurant + travel to customer, with traffic. Prep time from historical ML or restaurant-provided estimates. Push GPS to customers throttled (every 5–10 s) to save battery and bandwidth. Map tiles and routing can be delegated to a maps provider — do not build a full Google Maps unless asked.
Scaling peaks
- Shard by city/region — lunch in NYC should not contend with SF databases (sharding).
- Queue restaurant accept notifications; never lose an order on push failure (retry via queues).
- Read replicas for browse; primary for place-order.
- If dispatch is overloaded, widen search radius or fall back to sequential assignment — keep placing orders.
Worked example
- User adds tacos, checks out; payment authorized; order O99 created.
- Restaurant accepts; prep ETA 15 min; dispatch schedules courier for minute 12.
- Courier C7 accepts offer; tracks to restaurant; marks picked up.
- Customer watches C7 on map; delivery confirmed; payment captured; rating prompt.
Interview narrative
Emphasize three-sided flow and the order state machine. Contrast with Uber (no restaurant prep) and e-commerce (no live courier). Draw discovery → order → dispatch → track, and keep payments as a dependency box. That is the DoorDash interview in one clean story.