Design a Food Delivery App (System Design)
Table of Contents + â
Youâve probably done this on a hungry evening, right?
- Alex opens a food app, sees a bunch of restaurants nearby, and taps one that looks good.
- Alex picks a biryani and a cold drink, hits order, and pays.
- Then comes the fun part. A little map shows up, and Alex watches a tiny scooter icon crawl across the screen, getting closer and closer to the door.
That whole thing feels smooth and almost magical. But inside itâs a big, busy system: finding restaurants near you, taking your order, finding a driver near the restaurant, and streaming that scooterâs location to your phone in near real time. Itâs a favourite system design interview question because it touches so much at once. So letâs build one together, step by step.
đŻ What Weâre Building
Letâs name the thing plainly before we draw any boxes.
- A food delivery app connects three groups of people: the customer whoâs hungry, the restaurant that cooks, and the delivery driver who carries the food over.
- The customer browses restaurants, places an order, and pays. The restaurant accepts and cooks. A driver picks it up and brings it to the door.
- The part everyone loves is the live map, where you watch the rider move toward you on a real-time map.
So our job is to build the system that makes all of that work together, smoothly, fast, and for a lot of people at once. Think Swiggy, Zomato, DoorDash, or Uber Eats.
đ Requirements
Before designing anything, a good engineer asks: what must this thing actually do? We split that into two buckets.
- A functional requirement is something the system must do, a feature you can point at.
- A non-functional requirement is about how well it does those things, like how fast or how reliable it is.
Hereâs what our app must do. These are the functional ones:
- Let the customer browse restaurants near them, with menus and prices.
- Let the customer place an order and pay for it.
- Match the order to an available delivery driver near the restaurant.
- Track the order live, so the customer can watch the rider approach on a map.
- Send updates along the way, like ârestaurant acceptedâ or âdriver is hereâ.
And hereâs how well it should do them. These are the non-functional ones:
- Low latency. Browsing restaurants and seeing the live map should feel instant. Nobody waits patiently when theyâre hungry.
- Reliable orders. Once Alex pays, that order must not get lost. Money is involved, so we canât be sloppy here.
- Real-time location. The scooter on the map should move smoothly, not jump around a minute late.
- Scalable. It should keep working during the dinner rush, when millions of people order at the same time.
Always ask before you design
In a real interview, donât jump straight to drawing boxes. First ask the interviewer which features matter and roughly how big it needs to be. Nailing the requirements before you design is half the score.
đ§Š Break It Into Services
Hereâs the thing, a system this big shouldnât be one giant program. If everything lives in one place, a small change to the menu code could accidentally break ordering, and thatâs scary. So we split the app into smaller, independent pieces, each owning one job. Each piece is called a microservice, which is just a small service that does one thing and talks to the others over the network.
Letâs name the services we need:
- Restaurant / Catalog service. Holds the list of restaurants, their menus, prices, and whether theyâre open. This is what you browse.
- Order service. Takes an order, tracks its status, and handles payment. This is the heart of the system, because money lives here.
- Driver-matching service. Finds an available driver near the restaurant and assigns them to the order.
- Location / Tracking service. Takes the constant stream of location pings from drivers and pushes the right ones to the right customers.
- Notification service. Sends out the âorder acceptedâ and âdriver arrivingâ messages as push notifications.
Why split it up like this? A few solid reasons:
- Each team can build and ship their service on its own, without stepping on each other.
- If the catalog service has a bad day, ordering and tracking can keep working.
- You can scale each one separately. The location service gets overloaded with pings, so it can grow on its own without dragging the rest along.
Want the full story on services?
This split into small independent services is a whole topic on its own. See Microservices Architecture for the deeper why and how.
đ Finding Nearby Restaurants and Drivers
Okay, first real problem. When Alex opens the app, we need to show restaurants near Alex, not every restaurant on the planet. And later weâll need to find drivers near a restaurant. Both are the same kind of question: âwhatâs close to this point on the map?â
- A query that asks âwhatâs near this locationâ is called a geospatial query. âGeospatialâ just means itâs about positions on the earth, using latitude and longitude (the two numbers that pin a spot on the map).
- The naive way is to look at every restaurant, measure how far each one is, and keep the close ones. That works for ten restaurants. It falls apart for a million.
- So instead we organise places by location ahead of time, so âfind whatâs nearbyâ becomes a quick lookup instead of a giant scan. That organising-by-location is called a geospatial index.
A common trick to build that index is geohashing, which chops the map into a grid of small boxes and gives each box a short code, so finding nearby places is just âlook in my box and the boxes around it.â
So instead of measuring distance to every restaurant, we just peek into Alexâs box and the boxes touching it. Fast, and it scales no matter how many restaurants exist.
đľ Matching a Driver
Now Alex has placed the order and the restaurant is cooking. We need a driver. This is the driver-matching serviceâs job, and itâs basically the same nearby-search trick, but for drivers instead of restaurants.
Hereâs how the matching goes:
- Find available drivers near the restaurant, using the same geospatial index. âAvailableâ means online and not already carrying an order.
- Pick one. A simple rule is the closest free driver, but you can also weigh in their rating or how busy they are.
- Offer them the order. The driverâs app buzzes and asks, âwant this delivery?â
But drivers are people, so things donât always go smoothly. We have to handle that:
- The driver might decline. Fine, we just offer it to the next nearby driver.
- The driver might not answer at all. So we set a timeout, a short countdown, and if they donât respond in time, we move on to the next driver automatically.
- We keep going down the list until someone accepts. If nobody nearby takes it, we widen the search area a bit.
Never assign without a yes
Donât just hand the order to the nearest driver and assume theyâll do it. Always wait for an actual accept. Otherwise an offline or unwilling driver gets stuck with an order, and Alexâs food sits cold at the restaurant.
đşď¸ Live Location Tracking
This is the part Alex loves, the little scooter creeping across the map. Letâs see how that works without melting our servers.
- The driverâs app keeps sending its current location, every few seconds, to our location service. Each little update is a location ping.
- The customerâs app needs to see those pings show up almost instantly, so the scooter moves smoothly.
- The trick is how we get those pings from our server to Alexâs phone in near real time.
The naive idea is to have Alexâs app ask the server âwhereâs the driver now?â over and over. Thatâs called polling, and itâs wasteful, because most of the time the answer hasnât changed yet. Thereâs a much better way:
- We use a WebSocket, which is a connection that stays open both ways, so the server can push new locations to the phone the moment they arrive, without the phone having to keep asking.
- So the driver pings us, we update the orderâs location, and we push it straight down the open connection to Alexâs app. The scooter moves.
- For updates when the app is closed, like âyour order is hereâ, we fall back to a push notification instead.
More on real-time connections
That open two-way connection is the same backbone chat apps use. Dig into it in WebSockets and Real-Time Communication.
đ§ž The Order Flow
Letâs walk through one order from start to finish. An order isnât a single moment, it moves through a series of stages, and each stage is a status we track and tell people about.
Hereâs the journey:
- Placed. Alex pays, and the order service saves it. Payment confirmed, order locked in.
- Restaurant accepts. The restaurant gets the order on their screen and taps accept. Now they start cooking.
- Driver assigned. The matching service finds a driver who says yes, and ties them to this order.
- Picked up. The driver reaches the restaurant, grabs the food, and marks it picked up. Now live tracking really matters.
- Delivered. The driver hands it over and marks it done. Order complete.
At each step, two things happen:
- The order service updates the status, so everyoneâs looking at the same truth.
- The notification service fires off a message, so Alex isnât left wondering. âYour order is on the way!â
Who sends all those messages?
The pings and alerts are handled by a service built just for this. See Design a Notification System for how that part is built.
đď¸ High-Level Design
Letâs zoom out and put all the pieces on one canvas. When you step back, the whole system is a set of boxes talking to each other.
Letâs read the boxes:
- Client apps. Three of them really: one for the customer, one for the driver, one for the restaurant. They all talk to the backend.
- API gateway. The single front door. Every request comes here first, and it routes each one to the right service. (It also handles things like checking who you are.)
- Services. Our microservices from earlier, each minding its own job.
- Databases. The catalog has its own store, orders have theirs. Separate services, separate databases, so they donât trip over each other.
- Cache. A small fast store in front of the catalog, because restaurant listings get read constantly.
- Queue. A waiting line for messages. The order service drops a âsend notificationâ task here, and the notification service picks it up when ready. This lets the order service move on without waiting.
- Live location store. A separate fast store just for the driver pings, since those come flooding in.
Thatâs the full picture. Each part has one job, and the gateway ties them together.
đ Scaling It
Now picture the dinner rush, millions of people ordering at once, drivers pinging their location every few seconds. One server wonât survive that. Hereâs how we grow.
- Cache the restaurant listings. Most people in an area see the same nearby restaurants. So we keep those listings in a cache, a small fast in-memory store, and serve them without hitting the database every time. Browsing stays fast.
- Go async with a queue. Things like sending notifications donât need to happen right now while Alex waits. So the order service drops the task in a queue and moves on. A separate worker handles it a moment later. This is called working asynchronously, meaning we donât block and wait. It keeps the slow stuff from dragging down the fast stuff.
- Use a fast store for live locations. Driver pings come in a flood, and they donât need to live forever. So we keep them in a quick in-memory store (like Redis) instead of a heavy database. Reads and writes there are lightning fast.
- Shard by city or region. Sharding means splitting one giant database into smaller pieces so no single machine holds everything. Food delivery is naturally local, an order in Mumbai never touches a driver in Delhi. So we split the data by city or region. Each regionâs traffic stays on its own slice, and adding a new city just adds a new slice.
Put together, this handles huge load. Browsing flies through the cache, slow work goes async through the queue, locations live in a fast store, and sharding by region keeps each cityâs load on its own machines.
đ§° Tech Choices
Part of system design is not just naming pieces, itâs saying why you picked each one. Here are the main technology decisions for this system and the reason behind each.
| Decision | Choice | Why |
|---|---|---|
| Find nearby restaurants/drivers | Geospatial index | Finds whatâs nearby without scanning everything. |
| Track live driver location | Redis (in-memory) | Locations change constantly, too hot for a SQL database. |
| Show the live map | WebSockets | Pushes the driverâs moving position to the user. |
| Orders | Relational database | Durable, consistent record of each order. |
| Notify restaurant / async work | Message queue | Keeps the order request fast and reliable. |
â ď¸ Common Mistakes and Misconceptions
A few things trip people up on this one. Letâs clear them out.
- âScan all drivers to find the nearby ones.â Please donât. Checking distance to every driver in the country for each order is painfully slow. Use a geospatial index so âfind drivers near hereâ is a quick lookup, not a giant scan.
- âHave the app poll the server for the driverâs location.â Wasteful. Asking âwhere is it now?â over and over burns battery and server power, mostly to hear âsame as before.â Use a WebSocket so the server pushes new locations only when they actually change.
- âOne big database for everything.â Risky and slow. Orders, restaurant menus, and live locations have totally different needs. Give each service its own store, keep live locations in a fast in-memory store, and shard by region.
- âAssign the order to the nearest driver, done.â Not so fast. Drivers can be offline, busy, or just decline. You need accepts, declines, and timeouts, so an order never gets stuck on someone who wonât take it.
- âNotifications must be sent right away, in the order flow.â They donât have to block anything. Drop them on a queue and send them asynchronously, so a slow notification never holds up Alexâs order.
đ ď¸ Design Challenge
Try extending the design yourself. Think each one through first, then open the answer to see a full breakdown.
Surge pricing. When demand spikes and free drivers are scarce, prices go up. How would you measure demand vs supply in a region, and where would you apply the higher price?
ETA prediction. Show a smart âarriving in 25 minutesâ estimate. What signals would you use?
Batched deliveries. Let one driver carry two nearby orders at once. How does matching change, and how do you keep both customersâ tracking accurate?
đ§Š What Youâve Learned
You can now design a food delivery app from scratch and talk through it clearly. Hereâs what you picked up.
- â The core job: discover restaurants, place an order, match a driver, and track the rider live.
- â Functional vs non-functional requirements, and why you gather them first.
- â Splitting the app into services: catalog, order, driver-matching, location, and notifications.
- â Finding nearby restaurants and drivers with a geospatial index instead of scanning everything.
- â Matching a driver near the restaurant, handling declines and timeouts.
- â Live tracking with location pings pushed over WebSockets, with push notifications as a fallback.
- â The order flow through status stages, with a notification at each step.
- â Scaling with caching, async queues, a fast location store, and sharding by region.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
How do you find restaurants or drivers near a user without scanning everything?
Why: A geospatial index groups places by location, so you only look in the user's grid box and the boxes around it instead of scanning everyone.
- 2
Why push the driver's location over a WebSocket instead of polling?
Why: An open two-way connection lets the server push each new position the moment it arrives, instead of the app repeatedly asking.
- 3
How does driver matching handle a driver who doesn't respond?
Why: Offering to one driver with a short timeout means a slow or absent driver never freezes the match; the system moves to the next driver.
- 4
Why are live driver locations kept in a fast in-memory store rather than a normal SQL database?
Why: Pings arrive constantly and are throwaway, so a fast in-memory store absorbs the firehose and answers 'who's nearby' instantly.
đ Whatâs Next?
This case study leans on two ideas that show up across many systems. Go deeper on them next.
- Design a Chat Application digs into real-time messaging over WebSockets, the same backbone our live tracking uses.
- Design a Notification System shows how all those âorder acceptedâ and âdriver arrivingâ alerts actually get built and sent at scale.
Once youâre comfortable with those, come back and try the design challenge again. Youâll see the whole system click into place.