InterviewKata
System design · Worked design D13 of 46 · Location and marketplaces · Long form

Design ride hailing

A rider taps "Request" and within seconds one nearby driver, and only one, gets the trip. Behind that tap: hundreds of thousands of drivers reporting their position every few seconds, an index that answers "who is near this point?" instantly, and a matching step that must never give one driver two trips or one trip two drivers. It follows the interview framework phase by phase.

1. The prompt

"Design Uber: riders request a ride from where they are to a destination, see a price estimate, get matched with a nearby driver, and track the car on a map until the trip ends and payment is taken."

Two very different workloads meet here: a firehose of location updates that only need to be roughly right, and a small number of matching decisions that must be exactly right. Saying this split out loud early is the key to the design.

2. Frame the problem

How many drivers online, and how often do they report location?

About 1M at peak, every few seconds.

~250,000 location writes a second: they can't go to a disk database.

How fast must matching be?

A driver accepts within seconds; the rider sees the match within ~10 s.

Offers go to one driver at a time with a short timeout.

Can a driver ever get two trips at once?

Never. And a request never gets two drivers.

Matching needs an atomic claim on the driver.

Does the rider track the driver live?

Yes, on a map, until pickup and during the trip.

A real-time channel per trip.

Is the price fixed when the rider requests?

Yes: the estimate the rider accepts is honoured.

Fare quotes are stored with an expiry.

Pooled rides, scheduled rides?

Not in this version.

They change matching into an optimisation problem.

One city or many?

Hundreds of cities worldwide.

Partition by city or region: matching is local.

3. What it must do

  1. A rider can get a fare estimate and request a ride.
  2. The system matches the request with a nearby available driver, who accepts or declines.
  3. Rider and driver track each other live, and the trip ends with payment.

Not now:

4. How well it must do it

TargetWhy this number
Correct matching: a driver has at most one trip; a request at most one driver consistencyDouble booking strands riders and angers drivers.
Fast matching: most requests matched within ~10 s speedRiders give up and try another app.
Fresh locations: positions at most a few seconds old speedStale positions pick drivers who've already driven away.
Scale: 1M drivers online, ~1,200 requests a second at peak load shapePeaks are local: a concert ends, it starts raining.
No lost requests: a request survives a server crash data safety · surviving failuresA rider waiting for a match that silently vanished is the worst experience.

5. Numbers that change the design

QuantityWorking→ so we…
Location updates1M drivers ÷ an update every 4 s250,000 writes a second, each overwriting the last → so an in-memory geo index, not a disk database
StalenessA car at 50 km/h moves ~13.9 m a secondA 4 s interval means positions are up to ~56 m old: fine for matching
Ride requestsSay 20M trips a day, peaks ~5× average~230 a second average, ~1,200 at peak: modest, but each needs a correct match
Location history250,000 × 100 bytes~25 MB a second (~2 TB a day) → so a stream for analytics and trip records, off the matching path

6. The nouns

drivers     driver_id, status (offline | available | offered | on_trip), city, vehicle
geo index   city cell → {driver_id: (lat, lng, ts)}     in memory, TTL ~30 s
fares       fare_id, rider_id, pickup, destination, price, expires_at
rides       ride_id, fare_id, rider_id, driver_id, status (requested | matched | in_progress | done | cancelled)
locks       driver:{id} → ride_id   (SET NX with a TTL)

7. The contract

POST  /v1/fares {pickup, destination}         → {fare_id, price, expires_at}
POST  /v1/rides {fare_id}                      → 202 {ride_id}   (matching starts)
POST  /v1/drivers/location {lat, lng}          (every few seconds; often over a persistent connection)
PATCH /v1/rides/{id} {action: accept|decline}  (driver)
WS    /v1/rides/{id}/stream                    → driver position, status changes

The rider or driver comes from the session token. A driver can only update their own location and respond to offers made to them.

8. First working version

A location service writing to a drivers table, a ride service, and a matcher that queries for nearby drivers.

8.1 Reporting locations

Each driver app sends its position every 4 seconds; the location service updates lat and lng on the driver's row.

8.2 Requesting and matching

The ride service saves the request; the matcher queries drivers within a bounding box of the pickup, picks the nearest by straight-line distance, and sends that driver an offer.

8.3 Tracking

The rider's app polls the ride every few seconds for the driver's position.

Parked for later (section 9 fixes each)

  1. 250,000 row updates a second on a disk database, and a two-column range query to find drivers.
  2. Two matchers can offer the same driver to two riders at once.
  3. A matcher crash loses the request mid-offer.
  4. Straight-line distance isn't driving time.
  5. Polling for tracking is wasteful and laggy.
  6. Prices and payments must be exactly right.

9. Make it hold up

9.1 Storing and querying locations fresh locations · scale

ApproachWhy
✗ BadA drivers table with lat/lng columns, updated in place; query with WHERE lat BETWEEN … AND lng BETWEEN …250,000 updates a second thrash a disk database, and a range on two columns can't use one B-tree efficiently.
~ GoodA spatial index in the database (PostGIS, or a geohash column)Queries are efficient, but the write rate is still the problem: every update rewrites an index entry on disk.
✓ GreatAn in-memory geospatial index sharded by region: map each position to a cell (geohash, Google S2 or Uber's H3 hexagons), keep "cell → available drivers" in memory, with a TTL so drivers who stop reporting disappearUpdates are cheap overwrites; "near me" means look up the rider's cell and its neighbours. Redis GEO commands (GEOADD, GEOSEARCH) do this on a sorted set. Losing the index is fine: drivers report again within seconds.

Section 13 shows how the update interval trades write load against staleness. Design D14 explores the index structures (geohash grids vs quadtrees) in depth.

9.2 Matching without double booking correct matching · fast matching

9.3 Not losing ride requests no lost requests

Matching is a multi-step process with timeouts that can last a minute. Run it as a durable workflow (Module 22) or from a queue: the request is written first, and if a matcher crashes, another one resumes the same ride from its saved state. Peaks are local (a concert ends, it starts raining), so partition matchers by region.

9.4 Live tracking fresh locations

Once matched, the rider's app subscribes to the trip's channel over a WebSocket (Module 19). The driver's updates already flow through the location service; for active trips they're also published to the trip channel. Apps smooth the car's movement between updates so it doesn't jump, and drivers report more often during trips than while idle.

9.5 Pricing and trip records correct matching

The fare estimate is computed when requested and stored with an expiry, so the price the rider accepted is the price charged. Surge pricing compares demand and supply per cell, computed by a stream job over requests and available drivers. Trip records, payments and receipts go in a transactional database; payments follow Design D16 (idempotency keys, a ledger).

9.6 Cities, regions and failure scale · surviving failures

9.7 The whole design after the deep dives

A request matched through the final design.

10. Interviewer follow-ups

A stadium empties: 20,000 requests in 10 minutes from one area.

Matching for that city scales out (partitioned workers), surge pricing brings in drivers, and requests queue durably. Consider batch matching: collect requests for a few seconds and solve a small assignment problem for better overall pickup times.

Pooled rides.

Matching becomes an optimisation: insert a new rider into an existing trip if the detour is small. Candidates are drivers already on compatible trips; scoring uses the routing service. It's still one claim per seat, made atomically.

The routing service is slow.

Use cached ETAs per cell pair, or fall back to straight-line distance with a road factor, so matching continues with slightly worse choices.

A driver's phone loses signal mid-trip.

The trip continues; the app buffers locations and sends them when back. The rider sees "position unavailable" briefly. Fares use the recorded route when it arrives.

How do you test matching changes?

Replay recorded requests and driver positions in a simulator, compare pickup times and acceptance rates, then A/B test by city.

Multi-region failover for a city.

A city's live state (geo index, active offers) can be rebuilt quickly in another region from driver updates; ride and payment records are replicated. Active trips continue; new requests resume after failover.

Fraud: drivers faking GPS.

Check location plausibility (speed between points, roads), compare with network location, and flag outliers for review.

11. When things break

FailureWhat users seeHow we detect itWhat limits the damage
Matcher crashes mid-offerA few seconds' delayWorkflow step timeoutAnother matcher resumes the ride; the driver lock's TTL frees the driver
Geo index node lostMatching pauses briefly in one areaNode healthRebuilt from the next round of updates
Driver ignores an offerSlight delayOffer timeoutLock expires; next candidate offered
Lock store unavailableMatching stopsLock errorsFail closed for assignments (never risk double booking); replicated lock store
Payment provider slowReceipt delayedPayment latencyPayment is asynchronous after the trip; retries with idempotency keys

12. Wrap-up and the clock

Trade-offs we made:

What we'd watch:

At 10×:

A 45-minute interview

0–4Frame the problem (driver count, updates, double booking)
4–9Must do, how well, numbers (250K writes/s)
9–13Nouns and contract
13–20First working version, parked list
20–40Deep dives: geo index (9.1), locking (9.2), durable matching (9.3), tracking (9.4)
40–45Follow-ups and wrap-up

A 60-minute interview

0–5Frame the problem
5–11Must do, how well, numbers
11–16Nouns and contract
16–24First working version
24–50Deep dives 9.1–9.6 and the final picture
50–60Follow-ups: stadium surge, pooling, failover; wrap-up
LevelWhat a strong answer shows
Mid-levelLocation service, a geospatial lookup, a matching service, a trips database, WebSockets for tracking.
SeniorWrite-rate estimate driving an in-memory geo index, cell-based lookups, one-at-a-time offers with a TTL lock, durable matching, regional partitioning.
Staff+Batch matching and marketplace effects, surge computed by stream jobs, multi-region failover for a city, battery and network costs, which data needs strong consistency and which doesn't.

Common mistakes:

13. Try it, check yourself, read more

How often should drivers report their location? Try the numbers. Per-node throughputs are illustrative examples, not benchmarks.

Where should 250,000 driver location updates a second go?

Only the latest position matters and updates are frequent; memory handles the rate and lost data is re-sent within seconds.

How do you stop two riders being matched to the same driver?

A conditional update or lock means only one matcher can claim a driver; the TTL frees the driver if the offer times out.

Which data can be stale or lossy without harm?

The next update arrives in seconds; assignments and payments must be correct.

A matcher crashes halfway through offering a ride. What happens?

The request was saved first and matching runs as a durable workflow.
ResourceWhy
H3: Uber's hexagonal hierarchical spatial index (Uber)Why hexagonal cells, and how they're used for pricing and dispatch.
Redis geospatial (Redis docs)GEOADD and GEOSEARCH, the building blocks of a simple location index.
Design Uber (Hello Interview)Another walkthrough.