Ride sharing like Uber
Riders request a ride, nearby drivers get the offer, one accepts, and both see each other on a live map until drop off. Prices go up when demand is high.
In plain words
It's raining at Gulshan 1 at 6 pm. You open the app and tap Request. Behind the screen, the app looks at your square of the map and the squares around it, finds drivers who are free, and asks the one with the shortest drive to you first. He has 15 seconds to say yes. If he doesn't, the next driver gets asked.
Because of the rain, 30 people asked for a ride and only 10 drivers are free nearby. So the price goes up a step, say to 1.5x. That pulls more drivers toward Gulshan and makes some riders wait. You see the higher price before you book, and it is locked onto your ride.
Examples
- Uber, Lyft and Grab all match by area cells first, then by road time, not straight line distance.
- Two drivers tap Accept at once: a conditional database update lets only the first one win.
- Driver locations update every few seconds and are thrown away after; only the latest matters.
- Surge is recalculated every minute per area and capped, so prices can't run away.
1. Requirements
Functional
- Riders request a ride and see the price first.
- Nearby drivers are offered the ride; exactly one gets it.
- Both see each other live on a map until drop off.
- Price rises when riders outnumber drivers in an area.
Non functional
- Match within a few seconds.
- Never assign one ride to two drivers.
- Handle about 50k location updates per second.
- Location data can be lost; rides and payments can't.
Questions to ask
- One city or many? How many rides a day?
- Do we pick the driver, or offer to several and take the first accept?
- Is surge pricing in scope?
- Payments in scope, or just the ride?
2. Estimates
| rides | 1M a day, about 12 per second, peaks 5x at rush hour |
| drivers online | 200k, location every 4 s, so about 50k writes per second |
| match time | under a few seconds, from request to a driver accepting |
3. API
POST /rides { pickup, dropoff } { rideId, price }
POST /drivers/me/location { lat, lng } 204
POST /rides/{id}/accept { version } 200, or 409 taken
WS /rides/{id}/live driver position, ETA, status
4. Data model
rides(id, rider_id, driver_id, status, pickup, dropoff, price, surge, version) driver locations: Redis GEO, latest only, expire after 30 s surge(area_cell, multiplier, updated_at) recomputed every minute ride_events: every state change (audit + outbox to payments, receipts)
5. High level design
Follow one request at a time, top to bottom.
Request a ride
- Rider appPOST /rides with pickup and drop off
- Ride servicegets the surge price for the area, saves the ride as requested
- Matcherfinds free drivers in nearby map cells
- Redis GEOlatest driver positions
- Routing serviceranks candidates by road ETA
- Pushoffer to the best driver, 15 s timeout, then the next
Accept and ride
- Driver appPOST /rides/1/accept
- Ride serviceUPDATE ... WHERE status = 'requested'
- Pub/subdriver position forwarded to the rider every few seconds
- Ride eventseach state change, outbox to payments and receipts
Location updates
- Driver appGPS every 4 s over a socket
- Location serviceoverwrite in Redis GEO, expire after 30 s
- Surge jobevery minute: requests vs free drivers per area
6. Deep dives
How do you find the nearest driver?
Drivers are indexed by map cell (geohash or H3) in Redis. Look in the rider's cell and neighbours, keep free drivers, then rank by road ETA from a routing service, not straight line distance: a driver across a river is not close.
How do you make sure one ride gets one driver?
Offer to the best driver first, with a 10 to 15 second timeout, then the next. Accepting is a conditional update on status and version, so two accepts can't both win.
How does surge work?
Every minute, per area, compare open requests to free drivers. If riders outnumber drivers, raise the multiplier in small steps, with a cap. The rider sees and agrees to the price before booking, and it is stored on the ride.
How do both apps see live positions?
Drivers send GPS every few seconds over a persistent connection. The server forwards it to the rider of that ride through pub/sub. Positions are not stored in the main database; only the route summary is kept.
See the core idea run
Core logic (JavaScript, tested)
// Match a rider to the nearest free driver, and let the price follow demand.
// Distance here is straight line on a small grid; real systems use road ETA from a routing service.
const dist = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]);
function bestDriver(drivers, pickup, maxKm = 3) {
let best = null;
for (const d of drivers) {
if (d.busy) continue;
const km = dist(d.at, pickup);
if (km <= maxKm && (!best || km < best.km)) best = { id: d.id, km };
}
return best; // null means "no driver nearby": widen the search or say so
}
// Surge: when riders outnumber free drivers in an area, raise the price a little at a time.
function surge(requests, freeDrivers, { cap = 3, step = 0.25 } = {}) {
if (freeDrivers === 0) return cap;
const ratio = requests / freeDrivers;
if (ratio <= 1) return 1;
return Math.min(cap, 1 + Math.ceil((ratio - 1) / 0.5) * step); // round up to the next 0.25
}
7. Failures
| Driver accepts then goes offline | Heartbeat deadline. Missed, the ride is re-offered to the next driver, and the rider is told. |
| Location service overloaded | Drop pings, never queue them: only the newest one matters. |
| Payment fails at the end | The ride still completes. Charge again later from the outbox; the rider can't book again until it is settled. |
8. Trade-offs
- Offering to one driver at a time is fair and avoids wasted accepts, but slower than broadcasting.
- Straight line distance is cheap for finding candidates; road ETA is right for choosing between them.