Design Uber (Ride-Matching) — System Design Interview Walkthrough
"Design Uber" (or Lyft, or a food-delivery dispatch) is the canonical geospatial system design problem. Its heart is a question most other problems never ask: how do you efficiently find "the nearest available drivers" among millions of constantly-moving points, thousands of times per second? Get the location indexing and the matching flow right and the rest follows. Here's the walkthrough.
Step 1 — Clarify requirements
Functional
- Drivers come online and continuously report their location.
- A rider requests a ride from A to B.
- The system matches the rider with a nearby available driver and coordinates the trip.
- (Optional) ETAs, pricing/surge, trip tracking.
Non-functional
- Low-latency matching — a rider shouldn't wait long for a match.
- Very high write throughput — millions of drivers pushing location updates every few seconds.
- High availability and geographic scale (works per city/region).
- Reasonable consistency — a driver shouldn't be matched to two riders at once.
The two hard parts, stated up front: absorbing the flood of location updates, and querying "who's nearby" fast.
Step 2 — Back-of-the-envelope
Say 1 million active drivers, each sending a location update every ~4 seconds → ~250,000 location writes per second, continuously. That number alone tells you locations can't live in a normal transactional database updated per write — you need an in-memory, geo-optimized store. Ride requests are far fewer (thousands/second), but each triggers a spatial query.
Step 3 — The core problem: geospatial indexing
You need to answer "which available drivers are within X of this point?" quickly. A naive scan over all drivers is hopeless. The standard tools:
- Geohash — encode a lat/long into a short string where a shared prefix means spatial proximity. "Nearby" becomes a prefix match, which is easy to bucket and query. Simple and widely used.
- Quadtree — recursively subdivide space into quadrants; dense areas subdivide further. Good for uneven density (a busy downtown vs. empty suburbs).
- S2 / H3 — Google's and Uber's hierarchical cell systems that tile the globe into cells at multiple resolutions; a location maps to a cell, and neighbors are cheap to enumerate. (Uber famously uses H3.)
The idea in all three: bucket the map into cells, keep the set of drivers per cell, and answer a proximity query by looking at the rider's cell and its neighbors — turning a global search into a handful of small lookups.
Step 4 — Handling location updates
Driver locations are high-write and ephemeral, so keep them in a fast in-memory store (e.g., Redis, which has geospatial commands), keyed by geo-cell. Each update just moves a driver into the right cell. You don't need durable history of every ping on the hot path — persist a sampled trail asynchronously if you need it later.
A common design: a location service ingests updates (often through a queue to smooth spikes) and maintains the current driver-per-cell index in memory. This absorbs the 250k writes/second without touching the primary database.
Step 5 — The matching flow
When a rider requests a ride:
- Compute the rider's geo-cell.
- Query that cell and its neighbors for available drivers, expanding the radius if none are found nearby.
- Rank candidates (by distance/ETA), then dispatch the request to the best one.
- Reserve that driver so they can't be double-matched — mark them unavailable atomically while the offer is outstanding; if they decline or time out, release and try the next candidate.
That reservation step is where consistency matters: use an atomic operation/lock so two riders can't both grab the same driver. Matching itself can be greedy (nearest first) or batched (optimize assignments over a short window for better global efficiency) — mention the trade-off.
Step 6 — Trip lifecycle and related concerns
- During the trip, the driver keeps sending location updates, now streamed to the rider for live tracking (a real-time channel, like the chat problem).
- ETA/routing is typically a separate service (map + traffic data) — treat it as a dependency, not something you build inline.
- Pricing / surge is a separate concern from matching: it's computed from supply/demand per area, not baked into the dispatch loop. Keeping it separate is a clean-design signal.
Step 7 — Follow-ups interviewers love
- "How do you find nearby drivers efficiently?" Geo-cell index (geohash/S2/H3) + neighbor lookup — the crux.
- "How do you handle 250k location writes/second?" In-memory geo store, updates via a queue, no per-write DB hit.
- "How do you prevent a driver being matched to two riders?" Atomic reserve/lock while an offer is pending; release on decline/timeout.
- "What about a surge of requests in one area (a concert letting out)?" Hot cell / thundering herd — the neighbor-expansion and batched matching help; scale the region's shards.
- "Greedy vs. batched matching?" Greedy is simple and fast; batching over a short window yields better overall assignments at the cost of a little latency.
Common mistakes
- Scanning all drivers for each request instead of using a spatial index — the whole problem is avoiding that.
- Writing every location ping to a relational database — it won't survive the write volume; use an in-memory geo store.
- No reservation/locking — leads to double-matching a driver.
- Bolting pricing into matching — surge is a separate service driven by supply/demand.
Practice this out loud
The geospatial angle makes this problem distinctive, and interviewers dig into "how exactly do you query nearby drivers?" and "how do you not double-book?". On Whitepad, a ride-matching design is a preset problem: a senior AI interviewer runs it by voice, watches your whiteboard, and probes your geo-indexing, write-throughput, and reservation choices in real time — then scores you like a real interviewer. Talk through the matching loop until it's automatic.
Practice this out loud
Reading is the easy part. Sit across from a senior AI interviewer that talks, watches your whiteboard, and scores you like the real thing — your first mock is free.
Start a free mock →