Design Uber / Lyft (Ride-Hailing) โ System Design
Design a ride-hailing service end to end: the geospatial index (geohash/quadtree) to find nearby drivers fast, handling the driver location-update firehose in memory, matching, real-time tracking, geo-sharding, and surge pricing.
๐ High-Level Design · Senior → ๐ด the geospatial deep dive
"Design Uber / Lyft / a ride-hailing app." This is a fantastic question because it introduces a whole new dimension — location. The central puzzle: given a rider's spot, how do you find the nearest available drivers fast, out of millions of drivers whose positions change every few seconds? That's geospatial indexing, and it's the star of this design. Let's build it.
Step 1 — Requirements
- Functional: riders request a ride; match with a nearby available driver; both see each other's live location and ETA; handle the trip lifecycle (accept → pickup → drop-off).
- Non-functional: low latency (matching must feel instant), very high write volume (millions of drivers updating location every few seconds), highly available, and geographically distributed.
Step 2 — The two heavy flows
๐ Two things dominate: (1) drivers continuously send location updates — a massive, constant write stream — and (2) riders query for nearby drivers — a geospatial read. The design lives or dies on handling both efficiently.
Step 3 — The core: finding nearby drivers
๐ด The naive idea — compute the distance from the rider to every driver and sort — is hopeless at millions of drivers. You need a geospatial index that lets you ask "who's within ~2 km of this point?" without scanning everyone. Two standard techniques:
| Technique | Idea |
|---|---|
| Geohash | Encode a lat/long into a short string where a shared prefix = geographic closeness. "Nearby" becomes "same prefix" — a simple, indexable lookup. (Used by Redis GEO.) |
| Quadtree | Recursively split the map into 4 quadrants until each cell has a manageable number of drivers; to find neighbours, descend to the rider's cell and its neighbours. |
๐ด The key insight: divide the map into cells (grid squares). Each driver belongs to a cell based on their location. To find nearby drivers, you look only in the rider's cell and the adjacent ones — a handful of cells, not millions of drivers. Naming "geohash/quadtree to bucket drivers into geo-cells, then query only nearby cells" is the whole point of this interview.
Step 4 — Handling the location-update firehose
๐ด Drivers pinging their location every ~4 seconds is a write-heavy torrent. You don't durably write each ping to a slow database — instead you keep current locations in a fast in-memory store (like Redis, which has native geospatial support). The "where is everyone right now" state is hot, transient data — perfect for memory, updated in place, with the geo-index maintained as drivers move between cells. Actual trip records (for history/billing) go to a durable DB separately.
Step 5 — Matching & the trip
๐ The flow: a rider requests → a Matching Service queries the geo-index for nearby available drivers → picks one (by distance/ETA/rating) → offers the ride. Once accepted, rider and driver need real-time location sharing, so the app holds a WebSocket/persistent connection to stream positions and ETA live (like the chat system's connection model). The trip's state transitions (requested → accepted → started → completed) are tracked and finalised to the durable store for payment.
Step 6 — Architecture & scale
Microservices: a Location Service (ingests updates, maintains the geo-index), a Matching Service, a Trip Service, and a Pricing Service. Everything is sharded by geography — a city or region's data and servers are grouped together, which is natural because rides are inherently local (a rider in Delhi never needs drivers in Mumbai). This geo-sharding is how it scales globally. Async events (via Kafka) connect trip completion → payment, ratings, analytics.
Step 7 — A senior extra: surge pricing
๐ด When demand outstrips nearby supply in a cell, surge pricing raises the price for that area. Architecturally it's a service that watches the demand/supply ratio per geo-cell (data you already have from the location index) and adjusts a multiplier — a neat reuse of the same geospatial buckets.
The interview-ready walkthrough
"The core is geospatial indexing: I bucket drivers into geo-cells using geohash or a quadtree, so a rider's 'find nearby drivers' query only scans a few cells, not millions. Driver location pings are a write firehose, so I keep live positions in an in-memory geo-store like Redis, not a slow DB, with durable trip records separate. A Matching Service picks a driver by distance/ETA, then rider and driver stream live locations over a WebSocket. I shard by geography since rides are local, and reuse the demand/supply per cell for surge pricing." That leads with the geospatial insight — exactly what the question is about."
What to read next
- Design a Chat System — the real-time connection model reused here
- Caching Strategies — Redis as the live location store
- Database Sharding — geo-sharding by city/region
- ← The complete System Design guide (hub)