System design sample solution

System design: nearby restaurants for Uber Eats

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

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

The system design question

Design the backend that shows a customer the restaurants near them when they open Uber Eats. Focus on how you store restaurant locations and query them fast. What happens in a dense city center, or where there's nothing nearby?

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Storing restaurants and where they areRanking and personalization, beyond a simple sort
Finding the ones near a customer, fastMenus and ordering
Packed city centers and empty areasDelivery partners
Restaurants opening, closing and changing hoursPayments

Then agree on what "good" means:

  • Fast: the home screen waits on this, so aim for under 100 ms on the server.
  • Correct enough: don't show a place that's closed or can't deliver to you. A place that opened two minutes ago showing up a bit late is fine.
  • Works everywhere: a downtown block with 500 restaurants and a small town with 3 both get a sensible list.
  • Lots of reads: people open the app far more often than restaurants change.

Rough numbers (5 minutes)

Assume 1 million restaurants worldwide, 100 million app opens a day, and a peak around dinner about five times the average.

~6K
searches per second at dinner
~1 GB
to hold every restaurant's search fields
~1M
restaurants in total
<100 ms
target for one search
QuestionRough mathWhat it means
How many searches?100 million ÷ 86,400 × 5 for the peakAbout 6,000 a second at dinner
How big is the search data?1 million × about 1 KBAbout a gigabyte. It fits in memory on every search server
How often do restaurants change?Say each changes a few times a dayA few million small updates a day, tiny next to the reads
How many per area?A busy downtown cell might have a few hundredWe'll have to cut those down before sending them

The overall design (10 minutes)

Customer appsends its locationSearch servicecells, filter, sortLocation indexrestaurants per cellRestaurant databasethe full recordsChange streamkeeps the index fresh
Searches read an in-memory index of cells. The database is the source of truth and pushes changes to it.
  1. The app sends the customer's location and asks for nearby restaurants.
  2. The search service works out which map cells cover the area around them, and reads the restaurants listed in those cells from an in-memory index.
  3. It drops anything closed or too far to deliver, sorts what's left, and returns the first page.
  4. The app then fetches menus and photos for what it shows, mostly from a cache.
  5. When a restaurant changes in the main database, a change event updates the index within a few seconds.

What the apps need to do

ActionWho does itWhat's sent
Nearby restaurantsCustomer appLatitude, longitude, optional filters like cuisine, and a page cursor
Restaurant detailsCustomer appRestaurant ID, and we return the menu, photos and hours
Update a restaurantRestaurant toolsRestaurant ID and the new location, hours, delivery radius or open status

What we store and where

DataWhere it goesWhy
Restaurants: name, address, coordinates, hours, delivery radiusA relational databaseThe source of truth. It's small and has clear relations
Cell ID to restaurants in itIn memory on every search serverThe fast lookup for 'who's near this point'
Open or paused right nowIn the index, updated from the change streamRestaurants pause when they're busy, so it changes often
Menus and photosThe database, with a cache in frontRead a lot, changed rarely

Step 1: Storing locations so you can search them (10 minutes)

A normal index on latitude and longitude can't answer 'what's within 3 km'. It can narrow one column, but then it has to scan everything in that band. So turn a location into something one index handles well: a cell ID. That's geospatial indexing.

Cut the map into cells, like hexagons or a geohash grid, about 1 km across. Store each restaurant's cell ID next to its coordinates. Now 'near this point' means: find the customer's cell and its neighbors, grab everyone in those cells, and check the exact distance on that short list.

The schema stays simple. The restaurant table has coordinates, cell ID, hours, delivery radius and status, with an index on cell ID. For speed, the search servers keep a map from cell ID to a list of restaurants in memory, built from that table.

  • Delivery radius: a restaurant 4 km away with a 3 km radius shouldn't show up. Check each one's radius against the real distance.
  • Why not a geo database? You could, and saying so is fine. Explaining cells shows you know what it's doing underneath.

Step 2: Packed city centers and empty areas (10 minutes)

Fixed cells break at both ends. Downtown, one cell can hold 500 restaurants, so we'd read and sort a lot for every search. Out in the country, the customer's cell and its neighbors might be empty, even though there's a good place 8 km away.

For dense areas, use smaller cells, or stop once you've got enough. Search the customer's own cell first, then the ring around it, and keep going outward until you have, say, 200 candidates or you hit the max delivery distance. Downtown stops after one ring. A small town keeps going.

For empty areas, that same outward search just runs out of rings. If nothing's in range, return an empty list with a reason, so the app can say 'nobody delivers here yet' instead of a blank screen. Maybe show pickup options farther away.

  • Uneven load: a few downtown cells get most of the searches. Since every search server has the whole index, any of them can answer, so hot cells don't pile onto one machine.

Step 3: Keeping it fast and fresh (10 minutes)

Most of the time goes into reading candidates, filtering and sorting. Keeping the index in memory on each search server removes the network hop for the location part. With about a gigabyte of data, every server can hold all of it.

You could also cache whole results per cell, since everyone in the same small cell sees nearly the same list. Keep that cache for a minute at most, because open and paused changes fast at dinner time.

Freshness comes from the change stream. When a restaurant edits its hours or pauses orders, the database records it and an event goes to every search server, which updates its copy. A few seconds late is fine. If a server restarts, it loads a snapshot and then catches up on recent changes.

Where SQL fits

The restaurant records live in a relational database. They're small, have clear fields, and the restaurant tools need normal updates. The read path doesn't hit it at all, which is why SQL or NoSQL isn't the big question here. The in-memory index is.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Cells plus an exact distance checkScanning by latitude and longitude rangesOne cheap lookup gets a short list to checkWe needed shapes like real delivery zones, then store zones per cell
The whole index on every search serverSplit the index across machinesIt's only a gigabyte, and any server can answer any searchData grew way past one machine's memory
Searching outward in ringsOne fixed radius for everyoneDense areas stop early and empty areas keep lookingProduct wanted a strict 'within X km' list
A short result cache per cellNo cacheTakes a lot of repeat work off at dinner timeResults got personal enough that nobody shares them

What could go wrong (5 minutes)

What happensWhat we do about it
A search server diesThe load balancer sends traffic to the others, since they all have the full index
The change stream falls behindPaused restaurants show up for a bit. Alert on lag, and recheck status when someone opens a restaurant
The restaurant database is downSearches keep working from memory. Only restaurant edits fail
A restaurant has the wrong coordinatesIt shows up in the wrong place. Check addresses against coordinates when they're saved
Dinner rush in one cityAdd search servers. They're all the same, so it's easy
A server restarts with an old snapshotIt catches up from the stream before taking traffic

Follow-up questions (5 minutes)

How would you rank them better than by distance?

Get a few hundred candidates from the location index, then score them on distance, rating, delivery time and what this customer usually orders. The location index only has to find candidates.

What if the customer moves the pin to a different address?

It's the same search with a different point. The app just sends the new coordinates.

How would you show an estimated delivery time for each one?

Use a rough estimate from distance and how busy the restaurant is, saved per cell. Work out a real one only when they open the restaurant.

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–30Storing locations so you can search them
30–40Packed city centers and empty areas
40–50Keeping it fast and fresh
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 Uber.