The system design question
A rider asks for a trip. Find a driver close by and offer it to them. If they say no, try the next one. And a driver can never end up with two trips at once.
Agree on the scope (5 minutes)
You can't design all of Uber in an hour, so say up front what you're covering.
| We'll design | We'll skip |
|---|---|
| Drivers sharing their location | Payments |
| Finding available drivers nearby | Pricing and surge |
| Offering a trip to one driver at a time | Shared rides |
| Never double-booking a driver | Detailed arrival times |
Then agree on what "good" means:
- Fast: the first offer should go out within a couple of seconds.
- Correct: a driver can never be given two trips, and a trip can never get two drivers.
- Keeps running: one machine dying shouldn't stop people getting rides.
- Fresh enough: positions can be a few seconds old. Nobody will notice.
Rough numbers (5 minutes)
We're making these up, but they're reasonable. Say there are 1 million drivers online at a busy time, each sending their location every 5 seconds, and 10 million trips a day.
- 200K
- location updates per second
- ~100 MB
- to hold every driver's latest position
- ~120
- trip requests per second, on average
- ~600
- trip requests per second at a peak
A few more quick ones are worth doing, because they shape the design:
| Question | Rough math | What it means |
|---|---|---|
| How many machines for locations? | 200K updates ÷ 50K or so per machine | Plan on 5–10 machines |
| How many trips being matched at once? | 600 a second × about 20 seconds each | Around 12,000 at a peak |
| How many open driver connections? | 1 million ÷ 50K or so per server | About 20 servers |
| How much trip data? | 10 million a day × about 1 KB | About 10 GB a day, or 3–4 TB a year |
The overall design (10 minutes)
- Every driver's app sends its location every few seconds.
- A rider requests a trip. We save it and put it in a queue.
- A matching worker grabs the trip and looks up nearby drivers.
- The worker reserves the closest driver and offers them the trip.
- If they accept, we're done. If not, the worker tries the next driver.
What the apps need to do
| Action | Who does it | What's sent |
|---|---|---|
| Go online or offline | Driver | Their new status |
| Send location | Driver | Latitude, longitude and the time, every few seconds |
| Request a trip | Rider | Pickup, destination and a unique request ID |
| Check on a trip | Rider | Trip ID, and we return its status and driver |
| Answer an offer | Driver | Trip ID, and accept or decline |
What we store and where
| Data | Where it goes | Why |
|---|---|---|
| Latest driver positions | In memory | Changes constantly, and it's fine to lose |
| Available drivers by map cell | In memory | Needs to be very quick to search |
| Driver status and reservations | Relational database | Has to be correct |
| Trips | Relational database | It's the permanent record |
| Trips waiting to be matched | Queue | Holds work until a worker's free |
Step 1: Know where drivers are (10 minutes)
We only keep each driver's latest position, and we keep it in memory. A position is old news in a few seconds, so there's no point carefully saving every update to disk.
To search by location, split the map into a grid of cells about 1 km across, and keep a list of available drivers for each cell. That's geospatial indexing. When a location update comes in, we do three things:
- Work out which cell the driver is in now.
- If they've crossed into a new cell, take them off the old list and add them to the new one.
- Save their position and the time we heard from them.
Most updates don't change a driver's cell, so most of them are a quick overwrite. Only available drivers go on the cell lists. As soon as someone's reserved or on a trip, they come off, so they won't show up in searches.
200,000 updates a second is a lot for one machine, so we split the data across a few machines. Splitting by city is an easy place to start, since a rider and their nearby drivers are almost always in the same city. A huge city can get its own machines.
Step 2: Find nearby drivers (5 minutes)
- Work out which cell the rider's in.
- Grab the drivers from that cell and the eight around it. The closest driver might be just over the line.
- Throw out anyone we haven't heard from in the last 30 seconds, and anyone in the wrong kind of car.
- Sort by distance and keep the closest five or so.
- If nobody turns up, check a wider ring of cells, up to a limit.
Straight-line distance is fine to start. If you've got time, point out that the closest driver might be on the other side of a river, so you'd take that shortlist and rank it by estimated driving time. Doing that only for the top few keeps it cheap.
Step 3: Offer the trip without double-booking (15 minutes)
This is where most of your time should go. Two workers could pick the same driver for two different riders at the exact same moment. If a position's a little stale, nobody cares, but assignments have to be right. That's what people mean by consistency.
So we keep each driver's status in a relational database, and a worker reserves a driver with one update that basically says "mark this driver as reserved for this trip for the next 15 seconds, but only if they're still available."
The in-memory lists can be a moment behind, and that's fine. Worst case, a worker tries someone who just got reserved, the update fails, and it moves to the next driver. The database has the final say.
The steps for each trip
- Try to reserve the first driver on the list. If someone beat you to it, move on.
- Send the offer and give them 15 seconds.
- If they accept, assign the trip and you're done.
- If they say no or don't answer, release them and try the next one.
- If the list runs out, search further out. After a couple of minutes, tell the rider we couldn't find anyone.
Keep track of where each trip is
A trip can also end as cancelled, or no driver found, and it can go from Offered back to Matching when a driver says no.
Don't make workers sit and wait
Our numbers said there could be 12,000 trips being matched at once, and each is mostly just waiting on a driver. If every worker sat there for 15 seconds, we'd need a huge number of them. So a worker sends the offer, saves the trip's status and a timer, and moves on to something else. When the driver answers or the timer runs out, a worker picks it back up.
Why bother with a queue?
We don't want the rider's request hanging while all this happens. A message queue holds onto each trip until a worker finishes with it, and it helps when everyone leaves a concert at once. A separate queue per city stops one busy city from holding up another.
Getting the offer to the driver
Each driver's app keeps a connection open to our servers, so offers show up right away. If that doesn't work, fall back to a push notification. Riders can have the same kind of connection, or check every few seconds, to see when a driver's been found.
Small things that are easy to miss
- The rider taps twice: the app sends a unique request ID, so the second tap gets the same trip back.
- The rider cancels during matching: check the trip's status before each new offer, and release any reserved driver.
- The driver goes offline during an offer: the 15-second timer handles it, same as no answer.
Choices you should be able to defend
| We chose | Instead of | Because | We'd reconsider if |
|---|---|---|---|
| Offering to one driver at a time | Offering to several at once | It's fair and simple, and drivers don't lose trips they accepted | Riders are waiting too long |
| Positions in memory | Saving every update to a database | They go out of date in seconds | We needed a full location history, which would be a separate pipeline |
| Reservations in a database | Reservations in the in-memory store | We can trust it, and it's easy to reason about | Database writes became the bottleneck |
| A fixed grid | Cells that adapt to how busy an area is | It's easy to build and explain | Dense areas got too crowded |
Step 4: What could go wrong (5 minutes)
| What happens | What we do about it |
|---|---|
| A live positions machine dies | Nothing much. Drivers keep sending locations, so it refills in seconds |
| A worker crashes mid-offer | The reservation runs out and the trip goes back on the queue |
| A driver accepts too late | The reservation's expired, so we tell them the trip's gone |
| A driver's phone loses signal | Skip anyone we haven't heard from in 30 seconds |
| One area gets slammed | Use smaller map cells there, or split that city across more machines |
| The database's main server dies | A standby takes over. Matching pauses for a moment and picks up again from saved statuses |
| The queue backs up | Riders wait longer, so add workers and tell riders it's busy |
That 15-second expiry is doing a lot of work. Every reservation ends on its own, so a crash can't leave a driver stuck forever. Because the trip's status is saved, whichever worker picks it up knows exactly where things stand.
Follow-up questions (5 minutes)
What about a rider near the edge of a city?
The nearby cells might live on another city's machines. When a search reaches a border, ask both.
How would you make matches better than just closest driver?
Sometimes it's better to wait a second or two, gather a batch of riders and drivers in an area, and find the best overall pairing. It's slower for any single rider, but cuts total pickup time.
What if we expand to the whole world?
Run this setup separately in each region, so riders and drivers talk to servers near them. Trips rarely cross regions, which makes that pretty clean.
How would you read a heavy dashboard of live trips?
Keep it away from the database doing the matching. Copy changes somewhere else, and add caching for things that get read a lot.
How the 60 minutes add up
| Minutes | What you're doing |
|---|---|
| 0–5 | Agree on what you're building |
| 5–10 | Rough numbers |
| 10–20 | Draw the overall design |
| 20–30 | Driver locations |
| 30–35 | Finding nearby drivers |
| 35–50 | Matching without double-booking |
| 50–55 | What could go wrong |
| 55–60 | Follow-up questions |
Now try one out loud
Reading an answer is a lot easier than coming up with one. Practice with an AI interviewer who asks follow-ups.
Key concepts used here
- Geospatial indexingFind what's nearby with map grids like geohashes and H3.
- Sharding and consistent hashingSplit data across machines and keep it balanced.
- Consistency and the CAP theoremWhat happens when copies of your data can't agree.
- Relational vs non-relational databasesPick a database based on how you'll actually use it.
- Message queues and event streamsKeep services from depending on each other, and handle traffic spikes.
- Caching strategiesMake reads faster, and figure out what to do when data changes.
- Real-time deliveryPush updates to users the moment they happen, with WebSockets and friends.
Further reading
We wrote this sample answer ourselves. If you want to see how Uber has described parts of its real systems:
Practice Loop is independent and isn't affiliated with or endorsed by Uber.