What the interviewer is asking
"Design DoorDash" looks like Uber with restaurants added, and part of it is: couriers' live positions and the search for couriers nearby work the same way. What's new is that every order has three sides. A customer orders, a restaurant cooks, a courier carries, and each of them changes the order at a different moment. The interesting decisions are how one order moves safely through its states across several services, when to send the courier, and when one courier should carry two orders.
Below are the 8 steps of the interview framework: scope, data, API, the busy path, numbers, services, scale and deep dives.
1. Scope: what version 1 must do
- Order: browse restaurants nearby and their menus, order and pay.
- Cook: the restaurant accepts (or rejects) the order and cooks it.
- Deliver: a courier is assigned, picks up the bag and brings it.
- Watch it live: all three apps see the order's status as it changes.
Park the rest out loud, one line each: ratings, promo codes, grocery delivery and orders scheduled for later.
2. Data: restaurants, menus, orders and couriers
record fields
restaurant id, name, location, hours, prep_minutes
menu_item id, restaurant_id, name, price
order id, customer_id, restaurant_id, status, items (dish, qty, price paid), courier_id
courier id, name, status (free, offered, busy), position (replaced every 4 s)
The order copies the price Ana paid, so a menu change tomorrow can't change her receipt. The restaurant keeps how long its kitchen usually takes, which dispatch needs. A courier's position changes every 4 seconds and only the newest one matters, like a driver's in Uber.
3. The API: one order, three apps
POST /orders {: "thai-garden", : [{: "pad thai", : 1}], : "k81"}
→ 201 Created {: "o81", : "placed"}
POST /orders/o81/accept restaurant app (or /reject)
POST /orders/o81/ready restaurant app
POST /offers/f7/accept courier app
POST /orders/o81/picked-up courier app
POST /orders/o81/delivered courier app
The idempotency key is there for the weak signal: if the answer to Ana's tap is lost and her app sends the order again, the order service finds k81 already saved and answers with o81 instead of starting a second dinner (the Idempotency Keys lesson). Refusing "the same dishes within a minute" fails: people do order the same meal twice, and two copies arriving at once both pass that check. Each app keeps an open connection (a WebSocket, as in the Real-Time Updates lesson), so every status change is pushed to all three apps at once.
4. The busy path: placing an order at dinner
- Ana's app sends
POST /orderswith her dishes and key k81. - The order service checks that k81 is new and saves o81 as placed.
- Thai Garden's tablet shows the new order at once.
- Thai Garden taps Accept, and o81 becomes accepted.
- Ana's app shows that Thai Garden is cooking her order.
One write places the order; then a chain of status changes follows, each one news for someone else. That chain shapes the rest of the design.
5. The numbers
From the interviewer: 5 million orders a day, 40% of them at dinner between 18:00 and 20:00, and about 8 events per order (the six status changes below, plus "courier assigned" and "payment held").
what working
dinner orders 40% of 5 million = 2 million in 2 hours = 1 million an hour
orders a second 1 million ÷ 3,600 s ≈ 280, about 300 at the dinner peak
the day's average 5 million ÷ 100,000 s = 50 a second (about 58 with 86,400)
order events 8 × 300 ≈ 2,400 a second at the peak
couriers out 1 million orders an hour ÷ about 2 deliveries each = 500,000
courier positions 500,000 ÷ 4 s = 125,000 writes a second
The peak is about 5× the day's average, so design for dinner. A common slip: 2 million ÷ 120 minutes gives about 17,000 orders a minute, not a second. The orders are a modest load; the couriers' positions are the big one, as in Uber.
6. Services: every change is an event
When o81 changes, several parts need to know: the card, the restaurant's tablet, Ana's app, and a courier who must be found before the food is ready. Calling each one in turn makes every order as slow as the slowest of them; letting each one check the orders database every second floods it with empty reads.
Instead the order service saves each change and writes it as an event to an order events stream (the Pub/Sub and Streams lesson). Every other service reads it and does its own job:
- Dispatch service: plans the courier and sends the offer.
- Notifications: pushes the new status to the three apps.
- Payment service: holds, takes or releases the money.
The order service waits for none of them, and a new reader needs no change to it. Events are keyed by the order id, so one order's events land in one partition and every reader sees them in order.
7. Scale: a store for each kind of data
- Orders go in a database that never loses them. Saved once and changed about 6 times, 300 orders a second make about 2,000 writes a second: within one database server's 5,000, split by order id as the business grows.
- Couriers' positions, 125,000 writes a second, go in an in-memory location store split by area, and dispatch finds free couriers near a restaurant through map cells, as Uber finds drivers.
- What everyone reads goes in copies: restaurants near Ana from a search index by map cells (the Nearby Places lesson), menus from a cache refreshed when a restaurant edits its menu.
8. Deep dive: the order as a state machine
An order is always in exactly one state, and each state allows only a few next ones:
state may go to
placed accepted, rejected, canceled
accepted preparing, canceled
preparing ready
ready picked up
picked up delivered
delivered, rejected, canceled: final
The order service applies every change with one conditional update, as in the Concurrency and Locks lesson:
UPDATE orders SET status = 'ready'
WHERE id = 'o81' AND status = 'preparing';
-- 1 row changed: saved, and event e4 goes to the stream
-- 0 rows changed: a repeat (answer OK again) or a change the table forbids (refuse it)
The changes the apps sent for o81, in the order they arrived:
# change (sent by) o81 after event
1 placed (Ana) placed e1
2 accepted (Thai Garden) accepted e2
3 preparing (Thai Garden) preparing e3
4 ready (Thai Garden) ready e4
5 ready (Thai Garden) ready none: a retry, answered OK
6 delivered (Sam) ready none: refused, not picked up yet
7 picked up (Sam) picked up e5
8 delivered (Sam) delivered e6
Eight changes came in and six events went out. The second "ready" is the tablet retrying after a lost answer: it gets the same OK and changes nothing. The early "delivered" is refused, and the courier app asks Sam to confirm the pickup first.
There's a second kind of repeat. A stream delivers each event at least once: if the dispatch service reads e2, plans a courier and crashes before saving its place, it reads e2 again after the restart. So every reader keeps the ids of the events it has handled (the Message Queues lesson); otherwise two couriers drive to Thai Garden for one bag.
9. Deep dive: when to dispatch a courier
Thai Garden accepts o81 at 18:30 and needs about 15 minutes; Sam, the nearest free courier, is 6 minutes away.
offer goes out Sam arrives food ready who waits
18:30 (at once) 18:36 18:45 Sam, 9 minutes at the counter
18:45 (at ready) 18:51 18:45 the food, 6 minutes, cooling
18:39 18:45 18:45 nobody
So dispatch time = ready time − travel time: 15 − 6 = 9 minutes after the accept. The dispatch service sets a timer when the order is accepted; when it fires, it offers the order to the nearest free courier, reserved with a conditional update as in Uber. Travel times come from a maps service.
The ready time is an estimate from what the kitchen does now. At 19:00, in the rush, Thai Garden's last ten orders took about 25 minutes, not the 15 saved as its usual prep time. Planning with 25 sends the offer at 19 minutes; planning with 15 would send Lee at 9 minutes to wait 10 at the counter. Estimate prep from recent orders, and move the offer when the kitchen taps Ready early or falls behind.
10. Deep dive: batching two orders
Ana's pad thai is ready at Thai Garden at 18:45; Kim's tacos are ready at Taco Spot, 3 minutes away, at 18:48; Ana and Kim live 2 minutes apart.
plan Ana gets it Kim gets it couriers driving
a courier each 18:55 (10 minutes) 18:59 (11 minutes) 2 21 minutes
batched (Sam) 18:57 (12 minutes) 18:59 (11 minutes) 1 14 minutes
Sam's batched route: Thai Garden, Taco Spot (3 minutes), Ana (9 more), Kim (2 more). Ana waits 2 minutes longer; Kim doesn't, because Ana lives on her way; and a courier is free for another order. Batching is cheaper, and its price is a slightly later first order. So batch when the pickups are close, the drop-offs lie the same way and the first order's extra wait stays small, say under 5 minutes.
11. Deep dive: payments in the flow
event payment step
placed hold $25 on Ana's card (an authorization)
delivered take the $25 (a capture)
rejected, canceled release the hold
An authorization holds the money without taking it, and a capture takes it later, for the same amount or less. Charging in full at order time means a refund for every rejected or canceled order; charging only after delivery risks a declined card after the food was cooked and driven. Each call carries its own idempotency key (o81-hold, o81-capture), so a repeated event takes the money once, as in the Payment System lesson.
What breaks first
- A kitchen falls behind: couriers wait at the counter. Keep prep estimates fresh.
- A reader falls behind on the stream: news arrives late. Watch each reader's lag and add servers; partitions by order id let them share the work.
- No courier accepts: widen the search area and tell Ana the new arrival time.
Summary: the order to follow
- Scope: order, cook, deliver, all three sides watching live.
- Data: restaurants and menus, orders with a status and the price paid, couriers.
- API:
POST /orderswith an idempotency key; a call per step; status pushed to every app. - Busy path: placing an order at dinner, then its chain of changes.
- Numbers: about 300 orders and 2,400 events a second at the peak.
- Services: every change an event on a stream; dispatch, notifications and payments react.
- Scale: orders in a database, positions in memory, what everyone reads in copies.
- Deep dives: a state machine with conditional updates; offers at ready time − travel time; batching with a cap on the delay; hold at order, capture at delivery.