System design sample solution

System design: ride sharing

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

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

The system design question

Design a ride-sharing service like Uber. Drivers send their location, riders request a ride, and you need to match them quickly.

Agree on the scope (5 minutes)

Say up front what you're covering.

We'll designWe'll skip
Drivers sending their locationPricing and surge
Finding drivers near a riderPayments
Offering the ride and matchingMaps and turn-by-turn routes
Tracking the tripRatings and driver onboarding
Where trip data lives

Then agree on what "good" means:

  • Quick match: a rider sees a driver accepted within a few seconds.
  • One driver, one ride: a driver never gets two riders at once, and a rider never gets two drivers.
  • Fresh locations: the driver positions we search are only a few seconds old.
  • Nothing lost: once a trip starts, its record is safe even if a server dies.

Rough numbers (5 minutes)

Made up, but reasonable: 1 million drivers online at the busiest time, each sending a location every 4 seconds. About 10 million rides a day, with the evening peak at roughly 3 times the average.

~1M
drivers online at a peak
~250K
location updates per second
~10M
rides a day
~350
ride requests per second at a peak
QuestionRough mathWhat it means
How many location updates?1M drivers ÷ one every 4 secondsAbout 250K writes a second. Too many for a normal database
How much memory for live locations?1M drivers × about 100 bytesAbout 100 MB. Tiny. It all fits in memory
How many ride requests?10M ÷ 86,400 seconds, × 3 at a peakAbout 115 a second on average, maybe 350 at a peak. Small

The overall design (10 minutes)

Driver appslocation every 4sLocation servicesplit by cityMap cell indexin memoryMatching serviceoffers to one driverRider appsees the driver
Locations go into an in-memory index of map cells. A ride request looks up nearby cells and offers the ride to one driver at a time.
  1. Driver apps send their location every few seconds to a location service. It updates an in-memory index that maps each small map cell to the drivers in it.
  2. A rider asks for a ride. The matching service looks up the rider's cell and the cells around it, gets a short list of free drivers, and ranks them by how long they'd take to arrive.
  3. It offers the ride to the best driver. If they accept within a few seconds, the trip is created. If not, it moves to the next one.
  4. Once matched, the driver's location updates also go to the rider's app over an open connection, so they can watch the car arrive.
  5. Trips are written to a database. Location history goes to a stream for later use, like working out what the driver gets paid for the distance.

What the apps need to do

ActionWho does itWhat's sent
Send locationDriver appDriver ID, latitude, longitude, heading, and whether they're free
Request a rideRider appPickup and drop-off points, ride type, and a request ID so retries don't book twice
Accept or decline an offerDriver appOffer ID
Trip updatesServer, to the rider appDriver location, arrival estimate, trip status

What we store and where

DataWhere it goesWhy
Live driver locationsIn memory, map cell to drivers, split by cityUpdated 250K times a second and only the latest matters
Driver statusSame in-memory store, keyed by driverFree, offered, or on a trip. Changed with a check-and-set so only one ride wins
TripsRelational database, split by cityFew writes, needs to be correct, and has clear rules like one active trip per driver
Location historyA stream, then cheap storageUsed for fares, disputes and improving arrival estimates
Riders and driversRelational databaseSmall, rarely changes

Step 1: Tracking drivers and finding nearby ones (12 minutes)

You can't ask a normal database 'who's within 2 km of this point' 350 times a second while it's also taking 250K updates a second. Instead, cut the map into cells, using something like geohashes or hexagons. That's geospatial indexing: every point belongs to exactly one cell, and nearby points usually share a cell or sit in neighboring ones.

Keep a map from cell to the set of drivers in it. When a driver's update puts them in a new cell, take them out of the old one and add them to the new one. Most updates stay in the same cell and only change the stored position.

To find drivers near a rider, look in the rider's cell and the ring of cells around it. If there aren't enough free drivers, widen to the next ring. Then rank the short list by real driving time, not straight-line distance, since a driver across a river can be close on the map and 20 minutes away.

Split the index by city or region, which is sharding by place. Rides almost never cross cities, so each shard handles its own drivers and riders. A big city might need several shards, split by area.

Picking a cell size

Too big and every search returns thousands of drivers. Too small and you need lots of rings to find anyone in quiet areas. Something around a few hundred meters wide works for cities, and the widening rings handle the suburbs.

Step 2: Matching without double booking (10 minutes)

Two riders near each other can ask at the same moment, and both see the same free driver at the top of their list. If you offer both rides to that driver, someone gets let down.

So before offering, flip the driver's status from 'free' to 'offered' with a check-and-set: it only works if the status is still 'free'. One request wins. The other sees it failed and moves to its next driver. The offer has a timer, around 10 seconds. If the driver doesn't answer, flip them back to free and move on.

Offer to one driver at a time. Offering to several at once is faster, but then several drivers accept and all but one get told 'never mind', which drivers hate.

Riders tap the button twice and phones retry on bad networks. The request carries an ID made on the phone, and the server ignores a second request with the same ID. That's idempotency, and it stops two cars showing up for one person.

Step 3: Where the data lives, and what happens when things split (8 minutes)

Locations and trips want different things. Locations are a flood of tiny writes where only the latest one matters, so a lost update costs nothing. Trips are few, and they have to be right: one active trip per driver, a clear status, a fare. That's why locations sit in memory and trips go in a relational database. This is the SQL vs NoSQL question interviewers push on, and the honest answer is 'both, for different data'.

Then the CAP question: if part of the network splits, do you keep working or stop? For locations, keep working. A slightly old position is fine. For matching a driver to a trip, choose correct over available. It's better to make a rider wait a few seconds than to put one driver on two trips.

Keeping each city's matching on one shard helps here, since the check-and-set happens in one place and doesn't need machines in different places to agree.

Choices you should be able to defend

We choseInstead ofBecauseWe'd reconsider if
Live locations in memoryWriting every update to a database250K writes a second of data that's stale in 4 seconds isn't worth storing safelyWe needed exact location history for every second, which the stream already gives us
Map cellsSearching by distance on every requestLooking up a few cells is cheap and simpleWe needed very precise shapes, like airport pickup zones, which can be added as their own lookup
Offering to one driver at a timeOffering to several at onceNo wasted accepts, and drivers trust the offersMatching got too slow in quiet areas, then offer to two with a quick follow-up
Correct over available for matchingAlways answering, even during a splitDouble-booked drivers are worse than a short waitNever, for matching

What could go wrong (5 minutes)

What happensWhat we do about it
A location server diesIts drivers send their next update in 4 seconds to a new server, which rebuilds the index from those updates
A driver loses signalTheir position goes stale. Drop drivers with no update in 30 seconds from search, and keep the trip going
A driver accepts but the confirmation is lostThe driver app retries with the same offer ID, and the server returns the same answer
A concert ends and 10,000 people ask at onceMatching in that area runs short of drivers. Widen the search, queue requests, and let pricing handle the rest
The trip database is slowMatching holds the driver in 'offered' a bit longer. Locations keep flowing since they don't touch it

Follow-up questions (5 minutes)

How would you add shared rides?

Match a new rider to a car already on a trip if the detour is small. It's the same nearby search, but ranking now cares about the route, not only the pickup.

How do you estimate arrival time?

Use road travel times between cells, adjusted by recent real trips at that time of day.

What if a city is too big for one shard?

Split it by area. Searches near a border check the neighbor shard's cells too.

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–32Tracking drivers and finding nearby ones
32–42Matching without double booking
42–50Where the data lives, and what happens when things split
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 Bloomberg.