Free full guide

Uber (Ride Hailing)

Design Uber — a ride-hailing platform matching riders to nearby drivers in real time, with live location tracking, pricing, and trip lifecycle management.

00

Practice checkpoints

The interview rhythm stays compact, so the page can spend attention on the actual design decisions.

  1. 01
    Clarify scope
  2. 02
    Requirements + scale
  3. 03
    API + data model
  4. 04
    Draw architecture
  5. 05
    Deep dive
  6. 06
    Trade-off decision
01

Requirements that shape the design

Do not only state requirements. Ask for them. Each card pairs the design constraint with a clarification question you can say out loud before drawing the architecture.

Functional requirements

01What does the rider see first — a price before they commit?

Riders enter pickup and destination and get a fare estimate (price + ETA) before requesting the ride.

02What happens after they tap request — how fast must a match come back?

Riders request a ride at the estimated fare and get matched to a nearby available driver within about a minute. If no driver is available, they get a clear failure instead.

03What does the driver side do?

Drivers go online, send location pings, and receive one ride offer at a time to accept or decline. On accept, they navigate to pickup and drop-off.

04Can a rider cancel after matching, and what does the driver experience?

Yes. After a cancellation, the driver immediately becomes available for new rides, and the system records who cancelled and when.

05Does the rider watch the driver approach live?

Yes. The driver's location streams to the matched rider only while the ride is active. Everyone else sees rough availability, never a trackable trail.

06No driver accepts inside the matching window - then what?

The request fails clearly with retry guidance - a definite no within the minute beats a spinner that never resolves.

Out of scopeRatings (rider and driver) · Scheduled rides and ride tiers (X/XL/Comfort) · Surge pricing mechanics and payment settlement

Non-functional requirements

01Can one driver ever get two rides at once?

Matching is strongly consistent: a driver holds at most one active offer or ride at a time — no double-dispatch, ever.

02How fresh do driver locations need to be?

Drivers ping every few seconds while online; proximity search may see data a few seconds old, never minutes.

03What peak rate of driver location updates should we design for?

We design for a deliberate stress ceiling: 10M drivers online worldwide, each pinging every ~5 seconds. That works out to about 2M location writes per second.

04What happens when a stadium lets out?

A burst of ~100K ride requests from one area queues and drains gracefully — nearby zones and other cities stay unaffected.

05How fast should each step feel?

Fare estimate in a couple of seconds; match (or a clear failure) within about a minute end to end.

Keep asking — the interview is a conversation

Real interviews probe far more than a tidy list. These are the scope questions that separate candidates who interrogate the problem from those who recite it.

  • How long must ride history and driver location trails stay stored — and must a driver's trail be deletable on request?
  • Do we need to handle GPS spoofing or drivers gaming the matching, or is fraud filtered elsewhere?
  • Is this one city at launch or global from day one — and must each region's data stay in that region?
  • Is there a match success rate we are held to contractually, or is a clear failure within the minute acceptable?
  • Are surge pricing and ratings out of scope?
02

Numbers that force architecture decisions

Treat every estimate as a pressure that justifies a component: cache, queue, partition, replica, worker pool, or fallback path.

01

Location write firehose

a stress ceiling of 10M drivers online worldwide × 1 ping every 5 s10,000,000 ÷ 5 ≈ 2M writes/s

No relational database absorbs this — locations go into an in-memory geo index (geohash/H3 buckets) with TTL eviction.

02

Proximity lookup cost

The pickup point lands in one geohash cell; covering the search radius means that cell plus its 8 neighbors — a 3 × 3 grid, 9 cells9 cell reads × well under 1 ms per in-memory lookup ≈ single-digit milliseconds

Geohashing turns "who is near me" into a handful of key lookups instead of a table scan.

03

Concert burst

~100K requests from one neighborhood over ~10 minutes100,000 ÷ 600 s ≈ 170 matches/s in one zone

Partition the matching queue by geo zone — the spike saturates one zone’s consumers, not the whole city.

04

Offer budget

Match within 60 s; each offered driver gets ~10 s to respond60 s ÷ 10 s ≈ 5-6 drivers tried before the deadline

The 10-second offer lock limits how many drivers you can try inside the minute. So rank candidates well instead of spraying offers.

05

Phantom-driver cleanup

Location entries expire 15-30 s after the last ping — that is 3-6 missed pings at the 5 s cadence15-30 s ÷ 5 s per ping = 3-6 silent intervals → entry expires; a crashed driver stops receiving offers within ~30 s at worst

Drivers who crash or go offline vanish from the index on their own — no cleanup job, no offers to ghosts.

Decision example

The numbers

This system carries two very different loads. Drivers stream in about 2M location writes per second. Each ride match only needs a handful of nearby candidates within a minute.

My choice

Keep driver locations in an in-memory geo index (geohash/H3 buckets) with a short TTL, so drivers who stop pinging simply disappear. Matching pulls nearby candidates and offers the ride to one driver at a time, locking that driver for about 10 seconds. Accept wins the ride; a timeout or decline releases the lock and the next candidate gets the offer. During a spike, ride requests wait in a queue partitioned by area, so a stadium letting out slows that neighborhood, not the whole city.

Avoid

What I would NOT do: write every location ping into the main database and scan it to find nearby drivers. At 2M writes per second, that plus the scans kills it. I also would not offer one ride to several drivers at once without a lock, because two accepts can arrive together and both drivers think they got the job. And I would not shard the matching system on day one. Measure the per-zone rate first; sharding too early adds failure modes without adding capacity you actually need.

Change if

If match quality matters more than speed, like pooled rides or batching, I would collect requests for a few seconds and match them in small batches instead of one at a time.

03

Architecture path

One complete picture first, then each path as its own diagram — the write path and the read path carry different traffic and justify different components.

Complete picture

Overview — every component

Rider / DriverAppsAPI GatewayRide ServiceMatching ServiceGeo Index(in-memory)Ride DB (state)ride lifecycleNotificationServiceoffer → driverMapping Providerfare + ETA

Driver pings flow straight into the in-memory geo index; matching reads nearby candidates from it and offers rides one driver at a time. Ride truth lives in the database — the index is disposable.

Path 1

Request path — estimate, request, match

Rider AppRide ServiceMatching ServiceGeo Index(nearby drivers)Driver Offer(10s)

Fare first, then request. Matching pulls a handful of nearby candidates and offers one driver at a time; the 10-second lock prevents double-dispatch and moves on automatically when a driver ignores the offer.

Path 2

Location path — the write firehose

Driver AppAPI GatewayLocation ServiceGeo Index(geohash/H3)TTL Eviction

About 2M pings per second land in memory, bucketed into geohash/H3 cells; entries expire after 15-30 seconds so drivers who stop pinging vanish on their own.

04

API and data model

Before optimizing, make the contract inspectable: endpoints, entities, ownership, retries, and state.

POST/fare

req{ pickup, destination }

res200 { fare_id, estimated_fare, eta }

Calls the mapping provider for route + ETA; the estimate is saved so the ride request can reference it.

POST/rides

req{ fare_id }

res201 { ride_id, state: requested } · 404 fare expired

Kicks off matching; the rider gets the match result pushed (or polls).

POST/drivers/location

req{ lat, lng }

res200

Driver identity comes from the session token, never the request body. High-frequency write straight into the geo index.

PATCH/rides/{ride_id}

req{ accept | decline }

res200 ride · 409 offer expired

Accept flips the offer atomically; decline or a 10-second timeout releases the driver and the next candidate gets the offer.

Core entities

Rider

rider_id (PK) · payment_profile

Driver

driver_id (PK) · vehicle · status: offline/available/offered/on_trip

The status field is the double-dispatch guard: only an available driver can receive an offer, and the flip is atomic.

Fare

fare_id (PK) · pickup · destination · estimated_fare · eta

Created at estimate time; the ride request references fare_id so the quoted price cannot silently change.

Ride

ride_id (PK) · rider_id · driver_id · fare_id · state: requested/matched/in_progress/completed

DriverLocation

driver_id · lat/lng · updated_at

Lives in the in-memory geo index (geohash/H3 cells with a TTL), not the relational store — it is disposable data.

05

Deep dive directions

Pick one lane for the final third of the interview. Each lane gives you the topic, the interviewer question it should answer, and the failure mode to avoid.

Focus

The location firehose

Ask

10M drivers ping every 5 seconds. Where do 2M writes per second go, and how do you answer "who is near this pickup"?

Answer

Pings go to an in-memory geo index, never the relational database. It can't take 2M writes a second. Bucket each location into a geohash or H3 cell with a short TTL. To find who's near a pickup, read that cell plus its 8 neighbors — nine memory lookups in single-digit milliseconds, not a table scan.

Avoid

Writing pings to the main database and scanning for proximity — it dies at this rate.

Focus

Never dispatch one driver twice

Ask

Two ride requests want the same nearby driver at the same moment. How does exactly one win?

Answer

Grab the driver with a short exclusive lock. It's an atomic compare-and-set, so exactly one request wins and the loser moves to its next candidate. The database holds the real ride state: accept flips the driver to on-ride, and a cancellation releases them back to available so no stale hold lingers.

Avoid

Offering to many drivers at once with no lock — two accepts both think they won.

Focus

Driver does not answer

Ask

The offered driver ignores the request. What happens at second 10, and how does the ride still match inside a minute?

Answer

The offer is a 10-second lock, not a blocking wait. At second 10 it expires on its own and matching moves to the next ranked driver. At about 10 seconds each, 5-6 drivers fit inside the 60-second budget. That's why you rank candidates carefully instead of spraying offers everywhere.

Avoid

Blocking the ride on one unresponsive driver instead of a lock TTL that moves on.

Focus

A stadium lets out

Ask

100K people request rides from one neighborhood in minutes. How do you keep the rest of the city unaffected?

Answer

Partition the matching queue by geo zone. 100K requests over 10 minutes is only about 170 matches per second. It saturates that one zone's consumers while every other zone drains normally. The hot zone sees honest queueing — a longer wait or a clear failure. The rest of the city runs fine.

Avoid

One global matching queue — a local spike becomes a citywide outage.

Focus

Phantom drivers

Ask

A driver app crashes while marked available. How long can they still receive offers, and what cleans them up?

Answer

Every location entry expires 15-30 seconds after the last ping. At the 5-second cadence that's just 3-6 missed pings, so a crashed driver drops out of the index on its own. No cleanup job is needed. The longest a ghost can keep getting offers is about 30 seconds.

Avoid

No TTL on location entries — offers keep going to drivers who left an hour ago.

Ready to practice?

Talk through Uber (Ride Hailing) out loud and get AI scoring on the explanation.

Practice this with AI →