Classic design · Senior
Ride-hailing app (Uber)
Classic interview design. Key points: geospatial indexing, WebSocket and presence, and distributed locks.
Short answer
Classic interview design. Key points: geospatial indexing, WebSocket and presence, and distributed locks. In a ride-hailing app, the rider sees the estimated fare and requests the ride; the system has to match that request with a nearby, available driver, and the driver accepts or declines and proceeds to pickup.
01 · CONTEXT AND REQUIREMENTS
What dispatch needs to guarantee
In a ride-hailing app, the rider sees the estimated fare and requests the ride; the system has to match that request with a nearby, available driver, and the driver accepts or declines and proceeds to pickup. The heart of the problem is the despacho (who gets which request) and the ride state along that path.
Three non-functional requirements drive the design: low-latency matching, with a response (match or failure) in under a minute; strong consistency so that no driver receives several rides at the same time; and high throughput at peaks and events, with a hundred thousand requests coming from the same place.
02 · BASIC FLOW
From request to acceptance
The ride service creates the ride with the initial state of requested and triggers matching. The ride entity follows the request through to completion and holds, among other fields, the state, and it is this state that changes with each decision.
The rider confirms the fare and requests the ride; the gateway authenticates and forwards to the ride service.
The ride service writes the ride to the database with the initial state of requested.
Next it calls the matching service to find a driver.
In parallel, all the time, drivers send their position and the store keeps only the latest one for each.
Matching queries the nearby, available drivers, ranks them, and has the notification service alert the first on the list.
The driver accepts in the app; the request returns through the gateway and the ride service updates the state and assigns the driver.
The matching service picks the driver weighing proximity, availability, rating, and other factors. The notification service alerts the driver when a request is matched to them, and the location service handles real-time positions.
Matching ranks the eligible drivers and notifies the first on the list via APNs or FCM. If that driver declines, the system notifies the next on the list; on acceptance, the ride service updates the state and assigns the driver.
03 · DRIVER POSITIONS
What feeds matching
With millions of drivers sending their position every few seconds, the write rate reaches millions of updates per second. Without optimization, finding drivers by latitude and longitude would require scanning the entire table.
One way out is an in-memory geospatial store, such as Redis: it serves writes and proximity searches with high throughput and low latency and even expires data on its own. For drivers who go offline, a periodic process removes anyone who has gone past a time limit without an update.
The main challenge is durability, but since positions refresh within a few seconds, the impact of losing data would be small. On the app side, adaptive intervals reduce the volume: the send frequency changes according to speed, direction, proximity to pending requests, and driver status.
04 · ONE DRIVER AT A TIME
Avoiding simultaneous requests to the same driver
Consistency in matching means asking one driver at a time for each ride and giving each driver one request at a time. The driver has a short window, ten seconds, to accept or decline before the system moves on to the next one.
| Criterion | Application lock | Status in the database | Lock with expiration in Redis |
|---|---|---|---|
| Coordination across instances | There is no central coordination and race conditions appear | The database coordinates who holds the lock | All instances compete for the same lock |
| If the lock owner crashes | It can get stuck | The in-memory timer is lost and the lock gets stuck | The lock expires on its own |
| Main cost | Lack of coordination | Cron or an extra routine to release it | Depends on the in-memory store |
The ride request is published to the queue instead of being handled directly by the matching service.
A worker picks up the request; it will only be marked as done after the matching succeeds.
Before contacting the driver, the worker tries to acquire that driver's lock, with an expiration time equal to the acceptance window.
If another worker tries the same driver now, the lock already exists and its request does not go on to that driver.
With the lock in hand, the worker sends the request to the driver by push.
If the driver accepts, the state goes to the database and the lock is released; if the driver ignores it, the lock expires on its own and the driver becomes a candidate again.
The lock is short-lived and ephemeral, so losing it is easier to recover from than losing durable data; even so, the store needs monitoring and failover because matching depends on it.
05 · PEAKS AND FAILURES
Requests that cannot be lost
Without a queue, if a matching instance crashes, the requests it was processing are lost. With a fila, each request enters the queue and matching consumes them in order of arrival, scaling horizontally if the queue grows.
By committing the offset only after finding the match, the request stays in the queue if the instance crashes and another instance picks it up.
What if the driver doesn't respond? A delay queue schedules a message for when the window expires and, if the ride is still unassigned, moves on to the next driver in the list. The cost is managing many delayed messages and canceling them correctly when the driver accepts.
A durable execution solves this in another way: it offers timeouts, retries, and state that survive crashes and service restarts. The workflow stores its state persistently and, if the service crashes, resumes where it left off. Uber is the original author of the Cadence project, which gave rise to Temporal.
The rides service starts a matching flow for the new ride.
The flow sends the request to the first driver in the list and waits for the response with a time limit.
The driver does not respond within the limit; the flow notices the expiration and moves on to the next one in the list.
At each step the progress is recorded in the flow's state store.
If the flow instance crashes in the middle of the wait, the saved state is still available.
Another instance resumes the flow from where it stopped; when a driver accepts, the database receives the final state.
06 · TAKEAWAYS
Each failure, one mechanism
| Problema | Mecanismo | Observation |
|---|---|---|
| Two matchers compete for the same driver | Lock with expiration in the in-memory store | The lock disappears on its own if nobody accepts |
| Matching instance crashes | Queue with offset committed after the match | The request goes back to another instance |
| Driver does not respond | Delay queue or workflow timer | Moves on to the next in the list |
Frequently asked questions
What is Ride-hailing app (Uber)?
Classic interview design. Key points: geospatial indexing, WebSocket and presence, and distributed locks.
What does the Ride-hailing app (Uber) design cover?
It is organized in these sections: What dispatch needs to guarantee, From request to acceptance, What feeds matching, Avoiding simultaneous requests to the same driver, Requests that cannot be lost, and Each failure, one mechanism.
How long is Ride-hailing app (Uber) and what level is it?
It takes about 16 minutes to read and is rated Senior level.
Now design it yourself
Run this case as an interview in the System Design school: you make the decisions, the tutor challenges them and you see how your design scores.