System design sample solution

System design: design a food delivery app

A step-by-step system design answer you could actually give in 60 minutes.

System design interview · Sample 60-minute answer · Not Meta's actual design · Independent, not affiliated with Meta

The system design question

Design a food delivery app like DoorDash. Customers browse restaurants and place orders, restaurants accept them, a courier is assigned, and the customer can follow the order until it arrives.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Browsing nearby restaurants and their menusSearch ranking and recommendations
Placing an order and payingCourier pay and tips
The restaurant accepting itRatings and reviews
Assigning a courierRefunds and customer support
Following the order until it arrives

Then agree on what "good" means:

  • Orders never get lost: once the customer is charged, the order has to reach the restaurant and end up delivered or refunded.
  • Live status: the customer sees each step, like 'accepted' or 'picked up', within a few seconds, and the courier's dot moves on the map.
  • Quick courier match: a courier is offered the order within a minute or so of the restaurant accepting it.
  • Fast browsing: the list of nearby restaurants loads in a few hundred milliseconds.

Rough numbers (5 minutes)

These are assumptions: 20 million orders a day, about 80% of them in a 4-hour window around lunch and dinner, 1 million couriers online at a busy time sending their location every 5 seconds, and each order checked on by the customer about 20 times. Meta's design round is often 45 minutes, so get through these quickly.

~1K
orders a second at dinner
~200K
courier location updates a second
~5K
status checks a second
~10 KB
per order, all in
QuestionRough mathWhat it means
How many orders at a peak?20 million × 80% ÷ (4 hours × 3,600)About 1,100 a second. Small for a database, but every one involves money
How many location updates?1 million couriers ÷ 5 secondsAbout 200K writes a second. That's the biggest load, and none of it needs to be kept forever
How often do customers check?20 million × 20 checks ÷ 86,400, more at dinnerA few thousand a second. Easy, if we push updates rather than have phones poll
How much order data?20 million × 10 KB a dayAbout 200 GB a day. A sharded SQL database handles it fine

The overall design (10 minutes)

Customer appbrowses and ordersOrder serviceowns the order's stateOrder eventsevery state changeCourier matchingfinds who's nearbyLive updatespushed to each app
The order service owns each order and publishes every change, which drives courier matching and the live updates to the customer, restaurant and courier.
  1. The customer opens the app and a restaurant service returns places nearby that are open and deliver to them, using a map index of restaurant locations.
  2. They place an order. The order service saves it, charges the card through a payment provider, and moves it to 'sent to restaurant'.
  3. The restaurant's tablet gets the order and accepts it with a prep time. That's a state change, saved and published as an event.
  4. Courier matching hears 'accepted', looks up couriers near the restaurant, and offers the job to one. When they accept, the order gets that courier.
  5. Couriers send their location every few seconds. A connection service pushes status changes and the courier's position to the customer's app until the order is delivered.

What the apps need to do

ActionWho does itWhat's sent
List nearby restaurantsThe customer appTheir location, plus a cursor for the next page
Get a menuThe customer appRestaurant ID
Place an orderThe customer appRestaurant, items, delivery address, payment method and an idempotency key
Accept or reject an orderThe restaurant tabletOrder ID and a prep time
Accept a delivery offerThe courier appOffer ID
Update statusThe courier appOrder ID and 'picked up' or 'delivered'
Send my locationThe courier appCourier ID, position and time, every few seconds
Follow my orderThe customer appOrder ID. Opens a connection for live updates

What we store and where

DataWhere it goesWhy
Orders and their itemsSQL database, split by order IDMoney and state changes need transactions
Order history of state changesSame database, one row per changeSo you can see what happened, and replay it
Restaurants and menusSQL database with a cache in frontChanges rarely, read constantly
Restaurant locationsMap index by geohash or H3 cellSo 'what's near me' is a quick lookup
Courier locationsIn-memory store, by map cell, overwritten every few secondsHuge write volume, only the latest point matters
Order eventsEvent stream, split by order IDDrives matching, notifications and live updates

Step 1: The order's states (12 minutes)

Write the order down as a state machine. Each step only goes forward, and only certain people can move it: placed, paid, sent to restaurant, accepted, courier assigned, picked up, delivered. Plus a few side exits like rejected and cancelled.

The order service is the only thing that changes an order's state. A change is one database update that checks the current state first, like 'move to picked up, but only if it's courier assigned right now'. If two things race, one wins and the other gets a clear no. A SQL database is a good fit, because you want that check and the write to happen together.

Every change also writes an event. Save the event in the same transaction as the state change, then a small worker sends saved events onto the event stream. That way you never change an order without telling anyone, or tell everyone about a change that didn't save.

Paying without charging twice

Phones retry when the network drops, so 'place order' can arrive twice. The app sends an idempotency key with it, and the order service returns the same order if it's seen that key before. Send the same key on to the payment provider too.

Authorize the card when the order's placed and only capture the money once the restaurant accepts. If the restaurant says no, release the hold and the customer's never charged.

Step 2: Finding a courier (10 minutes)

Couriers send their location every 5 seconds. Keep only the latest point, in memory, grouped by map cell. This is geospatial indexing: to find couriers near a restaurant, look in its cell and the cells around it.

When an order's accepted, the matcher finds free couriers nearby and scores them on how far they are from the restaurant, and whether they'll get there around when the food's ready. There's no point sending someone who arrives 20 minutes before the food does.

Offer the job to the best courier for about 30 seconds. If they say no or don't answer, offer it to the next one. While an offer's out, that courier is marked busy so they don't get two at once.

  • Start matching a bit before the food's ready, using the prep time the restaurant gave, so the courier arrives as it's being bagged.
  • If nobody takes it after a few tries, widen the search and tell the customer it's taking longer.

Step 3: Live updates to the customer (8 minutes)

Polling every few seconds from millions of phones is wasteful. Instead the app opens a long-lived connection, like a WebSocket, to a connection service. That service keeps a map of which order each open connection is following.

It reads the order events and the courier's location updates and pushes them to whoever's following that order. A push notification covers the big moments, like 'your courier is here', for people who've closed the app.

The connection can drop at any time. When the app reconnects, it first fetches the order's current state from the order service, then listens for changes. So a missed message never leaves the screen wrong for long.

  • Send the courier's position to the customer every few seconds at most. Their map can smooth the movement in between.
  • Status changes should feel instant. Location can be a few seconds behind.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
SQL for ordersA NoSQL storeTransactions make state changes and payments safeOrder volume grew past what sharding SQL could handle comfortably
Courier locations in memory onlySaving every point to a database200K writes a second of data that's stale in 5 secondsWe needed the full route for disputes, so we'd also log points to cheap storage
Offering one courier at a timeBroadcasting to several and taking whoever's firstCouriers don't race for jobs and get let downMatching was too slow in busy areas
Pushing updates over a connectionThe app polling for statusFewer requests and updates arrive fasterMost customers closed the app right after ordering

What could go wrong (5 minutes)

What happensWhat we do about it
The restaurant's tablet is offlineIf they haven't accepted in a few minutes, call the restaurant or cancel and release the payment hold
The payment provider times outRetry with the same idempotency key. If it's still unclear, check with the provider before charging again
A courier's phone stops sending locationsTheir last position goes stale. Stop offering them new jobs, and alert support if they're mid-delivery
The connection service restartsApps reconnect, fetch the current state, then carry on listening
A courier cancels after pickup was dueMove the order back to 'needs a courier' and match again, starting near the restaurant

Follow-up questions (5 minutes)

How would you show an arrival time?

Add up the prep time left, the courier's travel to the restaurant and from there to the customer. Start with map travel times, then adjust with past deliveries in that area.

Could one courier carry two orders?

Yes, if the restaurants are close and the drop-offs are on the way. The matcher looks for an order that fits a courier's current route before picking a free courier.

What happens at a huge dinner rush?

Orders are fine, the database barely notices. The squeeze is couriers. Show longer wait times, and keep matching close by before widening the search.

How the 60 minutes add up

MinutesWhat you're doing
0–5Agree on what you're building
5–10Rough numbers
10–20Draw the overall design
20–32The order's states
32–42Finding a courier
42–50Live updates to the customer
50–55What could go wrong
55–60Follow-up questions

Key concepts used here

Further reading

We wrote this sample answer ourselves.

Practice Loop is independent and isn't affiliated with or endorsed by Meta.