Design Uber / Ride Sharing
Millions of drivers report their location every few seconds, and a rider expects a match in under two seconds. The design centers on a geo index that takes millions of writes a second and still answers nearby queries fast.
1 · Requirements
The Prompt
Match riders to nearby drivers in <2 seconds, track real-time locations, and handle surge pricing.
Functional
- Riders request a ride with a pickup and dropoff and get an ETA and fare estimate
- Match the rider to a nearby available driver
- Track the driver's live location during the trip
- Apply surge pricing where demand outruns supply
Non-Functional
- A match happens in under 2 seconds
- Location writes keep up with millions of pings a second
- A driver is never matched to two riders at once
2 · Estimates
Drill This Phase First
Show the Answer and Estimates
Answer
About 2.5M a second
10M divided by 4 seconds is 2.5M writes a second, which is why driver locations live in memory with hourly snapshots to disk.
- Writes
- 10M active drivers pinging every 4 seconds: 2.5M location writes a second into the geo store
- Matches
- About 100,000 ride requests a minute: roughly 1,700 matches a second and 5,000 at peak
- Geo Index
- Precision 6 geohash cells of about 1.2 km: about 3M populated cells, an index that fits in 50 GB of Redis
- Memory
- 10M drivers at about 200 bytes each: about 2 GB, kept in memory and snapshotted to disk hourly
- Sockets
- 10M driver and 1M rider connections across about 20,000 gateway nodes
- Latency
- The 2 second match target keeps the matching service in the same availability zone as the geo index
3 · API
WS /driver/connect: driver pushes { lat, lng, ts } every 4s
POST /rides/request { rider_id, pickup, dropoff } → { ride_id, eta, fare_estimate }
POST /rides/:id/accept (driver) → { rider_contact, route }
GET /rides/:id → { status, driver_loc, eta }
POST /rides/:id/complete → { final_fare, receipt_url }Data Model
driver_locations (Redis GEO / KV):
driver_id (PK) : uuid
geohash : string (precision 6)
lat, lng : float
status : enum(available|on_trip|offline)
last_ping : timestamp
rides (wide-column, partition = ride_id):
ride_id (PK) : uuid
rider_id : uuid
driver_id : uuid?
pickup, dropoff : geo_point
status : enum(requested|matched|in_progress|done|canceled)
created_at : timestamp
fare : decimal
surge_zones (KV, partition = geohash5):
geohash5 (PK) : string
multiplier : float
expires_at : timestamp4 · High-Level Design
5 · Deep Dives
Geohash Cells and Neighbors
Bucket drivers by geohash at precision 6, about 1.2 km. A nearby search reads the pickup cell plus its eight neighbors, since a driver 50 meters away can sit across a cell edge. Dense cities can use a quadtree that splits busy cells further.
One Driver, One Rider
Two dispatchers can pick the same driver at the same moment. Claim the driver with an atomic compare-and-set on status, from available to on_trip, and move to the next candidate when the claim fails.
Surge Pricing
Count open requests and available drivers per geohash5 zone from the Kafka stream over a short window. Publish a multiplier per zone with an expiry, and show it to the rider before they confirm, so the quoted fare matches the charged fare.
6 · Wrap Up
The Short Version
Geohash/quadtree for nearby-driver lookup; drivers push location over WebSockets; pub/sub broadcasts the match. A key-value cache maps driver_id to last_location.