AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content
SD notes

System Design · Note 20 of 22

Design a Ride-Hailing App (Uber)

Intermediate · 8 min read · updated 14 November 2026

Quick answer

How do you design a ride-hailing app like Uber?

Drivers send their location every few seconds to a location service that keeps only the latest position in memory, indexed by a geospatial grid such as geohash cells. A ride request searches the rider's cell and its neighbours, ranks nearby drivers by ETA and offers the trip to one driver at a time. The trip then moves through a state machine stored in a database.

On this page
  1. Requirements
  2. Capacity estimate
  3. API
  4. Data model
  5. High-level design
  6. Location updates and the geospatial index
  7. Finding nearby drivers
  8. Matching and dispatch
  9. Trip state machine
  10. Bottlenecks and trade-offs
  11. Interview questions
  12. Common questions

Match riders with nearby drivers in seconds: the design is a stream of location writes, a geospatial index and a careful dispatch loop.

Requirements

  • Functional: riders request a ride and see nearby drivers; drivers go online and share location; match, track the trip live, compute the fare, pay.
  • Non-functional: matching in a few seconds; live positions; a driver never assigned twice; highly available in every city.
  • Write-heavy: location updates dwarf every other request.

Capacity estimate

Assumptions, not facts — the point is the shape of the load.

AssumptionValueDerivedValue
Drivers online at peak1 millionLocation writes1M ÷ 4 s = 250,000/s
Update interval4 sInbound bandwidth250,000 × 100 B = 25 MB/s
Bytes per update100Latest positions in memory1M × 100 B = 100 MB
Trips a day10 millionTrip requests10M ÷ 86,400 ≈ 116/s

API

HTTP
Copy
POST /drivers/location     {"lat": 12.9716, "lng": 77.5946, "ts": 1731571200}   → 204
POST /rides                {"pickup": {...}, "drop": {...}}                    → 201 {"rideId": "r81", "state": "Requested"}
POST /rides/r81/events     {"event": "arrive"}                                 → 200 | 409
WS   /rides/r81/track      (driver position and state pushed to the rider)

Data model

StoreHoldsWhy there
In-memory geo index (Redis-like)driver → latest position and cell; cell → drivers250k writes/s, only the latest matters
Trips DBtrip_id, rider, driver, state, pickup, drop, fare, timestampsDurable, transactional state
Location history (log or time-series)driver, position, tsRoute replay and fares, off the hot path

High-level design

  • Location service takes driver updates (often over a WebSocket) and writes the geo index.
  • Matching service finds candidates, ranks them by ETA, runs dispatch.
  • Trip service owns the state machine and the trips DB; fares and payments follow completion.
  • Updates are also appended to a queue for history and analytics.

Location updates and the geospatial index

  • Keep only the latest position per driver in memory; history goes to a log.
  • Index positions by cell: a geohash prefix, a fixed grid, or a quadtree.
  • A geohash halves the map, alternating longitude and latitude, one bit per halving.
  • Most updates stay in the same cell; only a cell change touches the index.
  • Partition the index by region — cities are independent — see database sharding.
Geohash: halve the map, one bit per halving

A geohash turns a position into a short code by halving the map again and again. Each halving writes one bit: 1 for the east or north half, 0 for the west or south.

0:01 / 0:21Step 1 of 6
Transcript · 6 steps

Finding nearby drivers

  • Compute the rider's cell; read it and its 8 neighbours.
  • Rank only those candidates by distance, then by road ETA.
  • Too few drivers? Widen the ring of cells and search again.
  • Quadtree instead: small cells where drivers are dense, large where sparse.
Nearby drivers: search the rider's cell and its neighbours

Each cell is about 500 m across (an assumption). Every driver's app sends its position every few seconds, and the location store keeps only the latest one, indexed by cell.

0:01 / 0:21Step 1 of 6
Transcript · 6 steps

Matching and dispatch

  • Offer to one driver at a time, nearest first, with a short timeout.
  • Lock the offered driver (a key with an expiry), so no other request can take it.
  • A timeout or decline releases the lock and moves to the next candidate.
  • Assign with a conditional write: succeeds only if the driver is still free.
Dispatch: offer the trip to one driver at a time

Matching takes the ranked candidates, driver 3, driver 2, driver 5, and offers the trip to one driver at a time, nearest first.

0:01 / 0:21Step 1 of 7
Transcript · 7 steps

Trip state machine

  • States: Requested → Matched → Arrived → In trip → Completed; Cancelled from the early states.
  • Each event applies only from its listed state: UPDATE … WHERE state = 'Matched'.
  • Duplicate or late events (retries, two apps) are rejected, not double-applied.
The trip state machine: events apply only from the right state

A trip is a state machine stored with the trip row. Each event names a transition; it applies only if the trip is in the state the transition starts from.

0:01 / 0:22Step 1 of 7
Transcript · 7 steps

Bottlenecks and trade-offs

  • Update interval: fresher positions cost writes and battery.
  • Hot cells: an airport or stadium overloads one cell; split it finer (quadtree) or shard by cell.
  • Straight-line vs ETA: distance is cheap; road ETA is right but needs a routing service.
  • Region failure: run each region independently; a matching outage must not lose trip state.

Interview questions

  • Why not store every location in SQL? 250k writes a second of which only the latest matters belong in memory; history goes to a log.
  • How do you find nearby drivers fast? Geohash or grid cells: read the rider's cell and its neighbours, rank only those.
  • How do you avoid double booking a driver? One offer at a time, an expiring lock, and a conditional assignment write.
  • How do you handle duplicate trip events? A state machine whose transitions are conditional on the current state.

Next: Design a Web Crawler · Before this: Design a File Storage Service (Dropbox)

Common questions

How often should drivers send their location?

Every few seconds is the usual trade-off — often about 4 seconds while online. More often gives fresher positions but multiplies write traffic and battery use; less often makes matching pick drivers who have already moved away.

What is a geohash?

A geohash encodes a latitude and longitude as a short string by repeatedly halving the map, alternating longitude and latitude, one bit per halving, five bits per base-32 character. Nearby points usually share a prefix, so a grid cell is a key you can index and look up.

Geohash or quadtree for finding nearby drivers?

A geohash grid is fixed and simple: cells are keys in a hash map or Redis, and updates are cheap. A quadtree splits busy areas into smaller cells and leaves empty areas large, which adapts to density but must be rebalanced as drivers move. Many designs start with a fixed grid.

Why search neighbouring cells as well as the rider's cell?

A driver just across a cell border can be closer than a driver in the far corner of the rider's own cell. Reading the eight neighbours covers every point within one cell width; if too few drivers are found, the search widens ring by ring.

How do you stop two riders getting the same driver?

Offer a trip to one driver at a time and lock that driver while the offer is open, with an expiry so a lost phone cannot hold the lock. The final assignment is a conditional write that succeeds only if the driver is still available.

Why model a trip as a state machine?

A trip passes through fixed states — requested, matched, arrived, in trip, completed or cancelled — and events from two apps arrive out of order or twice. Allowing only listed transitions, each written conditionally on the current state, rejects impossible events and makes retries safe.

Design Uber: Ride-Hailing System Design

Design a ride-hailing app like Uber: driver location updates, geohash and quadtree indexes for nearby drivers, dispatch and the trip state machine.

  • Subject: System Design
  • Level: Intermediate
  • Reading time: 8 min
  • Updated: 2026-11-14

How do you design a ride-hailing app like Uber?

Drivers send their location every few seconds to a location service that keeps only the latest position in memory, indexed by a geospatial grid such as geohash cells. A ride request searches the rider's cell and its neighbours, ranks nearby drivers by ETA and offers the trip to one driver at a time. The trip then moves through a state machine stored in a database.

Match riders with nearby drivers in seconds: the design is a stream of location writes, a geospatial index and a careful dispatch loop.

Requirements

  • Functional: riders request a ride and see nearby drivers; drivers go online and share location; match, track the trip live, compute the fare, pay.
  • Non-functional: matching in a few seconds; live positions; a driver never assigned twice; highly available in every city.
  • Write-heavy: location updates dwarf every other request.

Capacity estimate

Assumptions, not facts — the point is the shape of the load.

AssumptionValueDerivedValue
Drivers online at peak1 millionLocation writes1M ÷ 4 s = 250,000/s
Update interval4 sInbound bandwidth250,000 × 100 B = 25 MB/s
Bytes per update100Latest positions in memory1M × 100 B = 100 MB
Trips a day10 millionTrip requests10M ÷ 86,400 ≈ 116/s

API

POST /drivers/location     {"lat": 12.9716, "lng": 77.5946, "ts": 1731571200}   → 204
POST /rides                {"pickup": {...}, "drop": {...}}                    → 201 {"rideId": "r81", "state": "Requested"}
POST /rides/r81/events     {"event": "arrive"}                                 → 200 | 409
WS   /rides/r81/track      (driver position and state pushed to the rider)

Data model

StoreHoldsWhy there
In-memory geo index (Redis-like)driver → latest position and cell; cell → drivers250k writes/s, only the latest matters
Trips DBtrip_id, rider, driver, state, pickup, drop, fare, timestampsDurable, transactional state
Location history (log or time-series)driver, position, tsRoute replay and fares, off the hot path

High-level design

  • Location service takes driver updates (often over a WebSocket) and writes the geo index.
  • Matching service finds candidates, ranks them by ETA, runs dispatch.
  • Trip service owns the state machine and the trips DB; fares and payments follow completion.
  • Updates are also appended to a queue for history and analytics.

Location updates and the geospatial index

  • Keep only the latest position per driver in memory; history goes to a log.
  • Index positions by cell: a geohash prefix, a fixed grid, or a quadtree.
  • A geohash halves the map, alternating longitude and latitude, one bit per halving.
  • Most updates stay in the same cell; only a cell change touches the index.
  • Partition the index by region — cities are independent — see database sharding.
city area (longitude →, latitude ↑)0101010000010000011101100011001011011100100110001111111010111010Ddriver's code1lon0lat0lon1lat
Geohash: halve the map, one bit per halving.
  1. A geohash turns a position into a short code by halving the map again and again. Each halving writes one bit: 1 for the east or north half, 0 for the west or south.
  2. Bit 1 splits by longitude: the driver is in the east half, so the bit is 1. The code so far is 1, and the box keeps only that half.
  3. Bit 2 splits by latitude: the driver is in the south half, so the bit is 0. The code so far is 10, and the box keeps only that half.
  4. Bit 3 splits by longitude: the driver is in the west half, so the bit is 0. The code so far is 100, and the box keeps only that half.
  5. Bit 4 splits by latitude: the driver is in the north half, so the bit is 1. The code so far is 1001, and the box keeps only that half.
  6. Four bits name one of 16 cells; the driver's is 1001. Cells sharing a prefix are near each other, but 1001 and its neighbour 0011 share none, so searches also read adjacent cells. Real geohash writes every 5 bits as a base-32 character: 6 characters is about 1.2 km by 0.6 km.

Finding nearby drivers

  • Compute the rider's cell; read it and its 8 neighbours.
  • Rank only those candidates by distance, then by road ETA.
  • Too few drivers? Widen the ring of cells and search again.
  • Quadtree instead: small cells where drivers are dense, large where sparse.
12345678Rcandidates by distancedriver 3 · 283 mdriver 2 · 570 mdriver 5 · 602 m
Nearby drivers: search the rider's cell and its neighbours.
  1. Each cell is about 500 m across (an assumption). Every driver's app sends its position every few seconds, and the location store keeps only the latest one, indexed by cell.
  2. One tick later the drivers have moved. Only driver 3 from cell (1,3) to (2,3) changed cell, so the index moves just that entry; the rest overwrite their position in place.
  3. A rider requests a car. The server computes the rider's cell, (2,3), the same way it computes every driver's — no scan of all drivers.
  4. It reads the rider's cell and its eight neighbours, because a driver just across a cell border can be closer than one inside. 3 drivers are in those nine cells.
  5. Only those candidates get an exact distance: 3 at 283 m, 2 at 570 m, 5 at 602 m. Real systems rank by road ETA, not straight-line distance.
  6. Driver 3 ranks first. Had the nine cells held too few drivers, the search would widen to the next ring of cells and try again.

Matching and dispatch

  • Offer to one driver at a time, nearest first, with a short timeout.
  • Lock the offered driver (a key with an expiry), so no other request can take it.
  • A timeout or decline releases the lock and moves to the next candidate.
  • Assign with a conditional write: succeeds only if the driver is still free.
MatchingDriver 3283 m · timed outDriver 2570 m · declinedDriver 5602 m · accepted✓Ridermatched
Dispatch: offer the trip to one driver at a time.
  1. Matching takes the ranked candidates, driver 3, driver 2, driver 5, and offers the trip to one driver at a time, nearest first.
  2. Driver 3 gets the offer and is locked for about 10 seconds, so no other rider's request can be offered the same driver at the same time.
  3. Driver 3 does not answer before the offer expires. The lock lapses by itself, so a lost phone or a dropped connection cannot hold a driver forever.
  4. The offer moves on to driver 2, next on the list, which is now the one locked while it decides. Only one driver holds this offer at a time.
  5. Driver 2 declines. The lock is released at once and matching moves to the next driver on the list.
  6. The offer moves on to driver 5, next on the list, which is now the one locked while it decides. Only one driver holds this offer at a time.
  7. Driver 5 accepts. Matching records the trip against that driver in one conditional write and tells the rider who is coming.

Trip state machine

  • States: Requested → Matched → Arrived → In trip → Completed; Cancelled from the early states.
  • Each event applies only from its listed state: UPDATE … WHERE state = 'Matched'.
  • Duplicate or late events (retries, two apps) are rejected, not double-applied.
acceptarrivestartendcancelRequestedMatchedArrivedIn tripCompletedCancelledevent: endIn trip → Completed
The trip state machine: events apply only from the right state.
  1. A trip is a state machine stored with the trip row. Each event names a transition; it applies only if the trip is in the state the transition starts from.
  2. "accept" moves the trip from Requested to Matched. Written as UPDATE … WHERE state = 'Requested', two events racing cannot both apply.
  3. "start" arrives while the trip is Matched, before the driver has reached the pickup. No transition starts there, so the event is refused and the state is unchanged.
  4. "arrive" moves the trip from Matched to Arrived. Written as UPDATE … WHERE state = 'Matched', two events racing cannot both apply.
  5. "start" moves the trip from Arrived to In trip. Written as UPDATE … WHERE state = 'Arrived', two events racing cannot both apply.
  6. "cancel" arrives during the ride. In this table a trip in progress cannot be cancelled, only ended, so the event is refused.
  7. The driver ends the ride: In trip becomes Completed, which triggers fare calculation and payment once, off the hot path.

Bottlenecks and trade-offs

  • Update interval: fresher positions cost writes and battery.
  • Hot cells: an airport or stadium overloads one cell; split it finer (quadtree) or shard by cell.
  • Straight-line vs ETA: distance is cheap; road ETA is right but needs a routing service.
  • Region failure: run each region independently; a matching outage must not lose trip state.

Interview questions

  • Why not store every location in SQL? 250k writes a second of which only the latest matters belong in memory; history goes to a log.
  • How do you find nearby drivers fast? Geohash or grid cells: read the rider's cell and its neighbours, rank only those.
  • How do you avoid double booking a driver? One offer at a time, an expiring lock, and a conditional assignment write.
  • How do you handle duplicate trip events? A state machine whose transitions are conditional on the current state.

Next: Design a Web Crawler · Before this: Design a File Storage Service (Dropbox)

Common questions

How often should drivers send their location?

Every few seconds is the usual trade-off — often about 4 seconds while online. More often gives fresher positions but multiplies write traffic and battery use; less often makes matching pick drivers who have already moved away.

What is a geohash?

A geohash encodes a latitude and longitude as a short string by repeatedly halving the map, alternating longitude and latitude, one bit per halving, five bits per base-32 character. Nearby points usually share a prefix, so a grid cell is a key you can index and look up.

Geohash or quadtree for finding nearby drivers?

A geohash grid is fixed and simple: cells are keys in a hash map or Redis, and updates are cheap. A quadtree splits busy areas into smaller cells and leaves empty areas large, which adapts to density but must be rebalanced as drivers move. Many designs start with a fixed grid.

Why search neighbouring cells as well as the rider's cell?

A driver just across a cell border can be closer than a driver in the far corner of the rider's own cell. Reading the eight neighbours covers every point within one cell width; if too few drivers are found, the search widens ring by ring.

How do you stop two riders getting the same driver?

Offer a trip to one driver at a time and lock that driver while the offer is open, with an expiry so a lost phone cannot hold the lock. The final assignment is a conditional write that succeeds only if the driver is still available.

Why model a trip as a state machine?

A trip passes through fixed states — requested, matched, arrived, in trip, completed or cancelled — and events from two apps arrive out of order or twice. Allowing only listed transitions, each written conditionally on the current state, rejects impossible events and makes retries safe.

← Design a File Storage Service (Dropbox) · Design a Web Crawler →