Skip to content

Module 07 // Case studiesLesson 30 / 3426 min read

Design a ride-sharing service

A ride-sharing service tracks millions of moving drivers, finds the nearest available ones when a rider requests a trip, and makes sure each driver is matched to only one ride.

Think of it like this

Analogy

A taxi dispatcher with a big wall map. Every few seconds drivers radio in their position, and the dispatcher moves their pin. When a call comes in, the dispatcher looks at the grid square around the caller and the squares next to it, picks the closest free taxi, and radios them. Crucially, once a taxi accepts, its pin is flipped to “busy” so nobody else gets sent to it.

The problem it solves

Millions of drivers move constantly, sending location updates every few seconds. That's hundreds of thousands of writes per second that are stale almost immediately. When a rider requests a trip, the system must find nearby available drivers in milliseconds, offer the trip, and guarantee no driver is assigned to two riders, while showing both sides a live map.

A real example

Example

Booking a ride after a concert

A concert ends and 20,000 people open the ride app within ten minutes, all in the same few streets.

Every nearby driver's phone has been sending its location every few seconds. Those updates land in an in-memory geo index split into map cells, and only the latest position per driver is kept. When you request a ride, the matcher checks your cell and the cells around it, ranks the closest free drivers, and offers the trip to one.

Two riders can be matched with the same driver at the same moment. The driver is assigned with an atomic “set to busy only if still free” update, so the second request fails and moves on to the next driver. Prices go up in that area, which brings in drivers from nearby and spreads demand over time.

How it works

1. Requirements

Functional: riders request a ride and see nearby cars; drivers go online and receive offers; matching; live trip tracking; pricing estimate; payment after the trip.

Non-functional: matching within a few seconds; location updates handled at very high write rates; a driver never double-booked; highly available, especially for ongoing trips.

2. Estimates

5 M drivers online at peak, each sending a location every 4 s, is about 1.25 million writes per second. Each update is tiny (driver ID, lat, lon, heading, timestamp), so the challenge is the rate, not size. Ride requests are far fewer, say a few thousand per second.

3. Location updates

Drivers keep a persistent connection (a WebSocket or a lightweight protocol like MQTT) to location gateways. Each update:

  1. Writes the driver's latest position into an in-memory geospatial index, sharded by region or city.
  2. Is published to a stream for trip tracking, analytics and ETA models (history goes to cheap storage).

Only the latest position matters for matching, so the index overwrites rather than appends.

4. Finding nearby drivers

Divide the map into cells. A geohash (or Google's S2 or Uber's H3 hexagons) turns a point into a cell ID where nearby points share a prefix. To find drivers near a rider, look up the rider's cell and its neighbors, collect available drivers, and rank them by estimated time of arrival (road distance and traffic, not straight-line distance).

Redis-style stores have built-in geo commands that do this; at very large scale, a custom in-memory service per region holds the index.

5. Matching without double-booking

Matching: find candidates, then atomically reserve one driver.

The reservation is a conditional write (compare-and-set) on the driver's state. If it fails because another request got there first, move to the next candidate. That one step needs strong consistency; everything around it can be eventually consistent. If the driver doesn't accept in time, release them and try the next.

6. During the trip

The rider's app subscribes to the driver's location for that trip only. The trip itself (state machine: requested → matched → arriving → in progress → completed) lives in a durable database, because it's money. Fares are computed at the end from the recorded route; payment is processed asynchronously with idempotency keys (lesson 23).

7. Scaling by geography

Riders and drivers only interact locally, so partition (shard) the geo index and matching by city or region. Each region scales independently, and a problem in one city doesn't affect others. Busy downtown cells can be split further.

Trade-offs

What you gain

  • In-memory geo indexes handle very high update rates with fast nearby queries.
  • Regional partitioning matches how the business works and limits blast radius.
  • Strong consistency only where needed keeps the rest fast and available.

What it costs you

  • Location data is huge in volume and privacy-sensitive.
  • Persistent connections from millions of devices are hard to operate.
  • ETA quality (traffic, roads) needs specialized map services and models.

When not to use it

  • For a small local service with a few hundred drivers, a database with a geospatial index (like PostGIS) and polling is simpler.
  • Don't store every location update in your primary database.

In the cloud

Maps and locationEvery major cloud sells this as a managed service, so you configure it rather than build it.

AWS1

Azure1

Google Cloud1

How you’d set it up

Drivers send locations every few seconds; a geo index answers "who is near this rider?".

  1. Driver app
  2. API Gateway WebSocket
  3. Location updates in ElastiCache (geo)
  4. Matching service

Interview questions

Try answering out loud first, then open the model answer.

How would you store and query driver locations?

Model answer

Keep only the latest position per driver in an in-memory geospatial index, partitioned by region: either a cache with geo commands keyed by city, or a custom service using geohash or H3 cells mapping to sets of driver IDs. Updates overwrite the driver's entry and move them between cells when they cross a boundary. Nearby search reads the rider's cell plus neighbors, filters by availability and vehicle type, and ranks by ETA. Location history goes asynchronously to a stream and cheap storage.

How do you make sure a driver isn't assigned to two riders?

Model answer

Make the reservation atomic: a conditional update that changes the driver's state from available to offered only if it's currently available (a compare-and-set in a strongly consistent store, or a short lease). Only one matching request can win; the others move on to their next candidate. Offers expire after a timeout, releasing the driver. The final transition to “on trip” is also conditional.

What happens if the matching service in one city goes down?

Model answer

Because the system is partitioned by region, other cities are unaffected. Within the city, run matching as replicated, stateless instances with driver state in a replicated store, so another instance takes over. Ongoing trips continue because trip state is durable and drivers' apps keep working offline, syncing later. Riders get a clear “try again in a moment” message instead of silent failures, and requests can be queued briefly.

Quiz

Pick an answer to see whether it’s right and why.

  1. 01Drivers send their location every 4 seconds. Where should those updates go first?
  2. 02How does a geohash help find nearby drivers?
  3. 03Two riders' requests try to grab the same driver at the same moment. What prevents a double match?
  4. 04Why keep live driver locations in memory instead of writing each update to a SQL table?
  5. 05Where should the finished trip record (route, price, payment) go?
  6. 06What is surge pricing mainly for?

0 of 6 answered

Key terms

Geohash
A short string for a map cell; nearby places share a prefix, which makes "what's near me" lookups fast.
WebSocket
A long-lived, two-way connection so the server can push messages to the client at any time.
Cache
A small, fast store that keeps copies of data that is expensive to fetch, so repeat requests are quicker.
Strong consistency
Every read sees the most recent write, as if there were only one copy of the data.
Sharding
Splitting a dataset into pieces (shards) and storing each piece on a different machine.
Message queue
A buffer where producers leave messages and consumers pick them up later, so neither waits for the other.

Progress is saved in this browser only.