Design Uber

HardLocation-Based ServicesGeospatial IndexMatchingReal-TimeState Machine

Problem

Design a ride-hailing service. A rider requests a ride from their location to a destination, sees a price and ETA before confirming, and is matched with a nearby available driver within seconds. Both parties see each other's live location during the trip, and the rider pays automatically at the end. The service operates in hundreds of cities with 5 million active drivers, and 20 million trips per day.

  • How often do driver apps send location updates while online?
  • Should matching pick the nearest driver, or optimize across many requests in a batch?
  • Is dynamic surge pricing in scope?
  • Do we support shared rides with multiple riders, or only private rides?
  • What happens if a driver declines or does not respond?
  • Which payment methods and regions must we support at launch?

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • Riders request a ride and see an upfront price and pickup ETA.
  • Match each request to a nearby available driver within a few seconds.
  • Track driver location in real time and show it to the matched rider.
  • Charge the rider and record driver earnings when the trip ends.
  • Apply surge pricing when demand exceeds supply in an area.
  • Let riders and drivers rate each other after a trip.
  • Let either party cancel, with cancellation fees according to policy.

Items in bold are existing requirements. The rest are suggestions. Confirm with the interviewer whether they are relevant first.

  • 5 million drivers online at peak, each sending a location every 4 seconds: about 1.25 million location updates per second.
  • 20 million trips per day, about 230 per second on average and roughly 1,000 per second at peak.
  • Match latency under 5 seconds at p95 from request to driver offer.
  • A driver must never be assigned to two trips at once, and a trip must never be charged twice.
  • Highly available within each city; an outage in one region must not affect others.
  • Location data for active trips retained for safety and dispute resolution (about 1 KB per minute of trip).

Driver apps stream locations to a location service that keeps each driver's latest position in an in-memory geospatial index partitioned by city and cell. A request goes to a dispatch service that queries nearby available drivers, ranks them by ETA from a routing engine, and offers the trip to the best one with an atomic reservation. Trip state lives in a strongly consistent store driven by a state machine, while location streaming to the rider and payment run on separate paths.

Loading diagram...

Quote a price. The rider app sends pickup and destination. The pricing service asks the routing service for distance and duration, applies the city's rate card and the current surge multiplier for the pickup cell, and returns a quote id valid for a few minutes.

Request and match. The rider confirms with the quote id. The trip service creates a trip in REQUESTED state. Dispatch queries the geospatial index for available drivers in the pickup cell and neighboring cells, gets road ETAs for the closest few dozen, and picks the best. It reserves that driver with a conditional update (only if status is AVAILABLE) and pushes an offer over the realtime gateway. If the driver declines or 15 seconds pass, the reservation is released and the next candidate is tried.

Track the trip. Once the driver accepts, the trip moves to DRIVER_EN_ROUTE. Driver location updates are forwarded to the rider's connection and appended to the trip's location history. State moves to IN_PROGRESS at pickup and COMPLETED at drop-off.

Pay. On completion, the trip service computes the final fare from the actual route and calls the payment service with the trip id as the idempotency key. The payment service charges the stored card and credits the driver's earnings ledger.

Ingests driver location updates and keeps the latest position of every online driver searchable.

  • Realtime gateway holding WebSocket or MQTT connections from driver apps.
  • In-memory geospatial index using H3 hexagonal cells (or geohash), held in Redis or a custom service.

Diagram

Loading diagram...

Data Model

  • driver_location(driver_id) -> { lat, lng, heading, cell, status, updated_at }
  • cell_drivers(city, cell_id) -> set of driver_id for available drivers only.

Scalability

Shard by city, and split large cities by cell ranges, so each shard handles a bounded number of drivers. At 1.25 million updates per second each update is a small in-memory write; locations are not durably written per update, because the next update arrives in seconds anyway. Drivers that stop reporting for 30 seconds are marked offline.

Tradeoffs

Keeping only the latest location in memory (and streaming history to cheap storage separately) is what makes this write rate affordable. Writing every update to a durable database would require a huge cluster for data that is obsolete after four seconds.

Matches ride requests to drivers.

  • Stateful service sharded by city, with an in-memory view of open requests.

API

  • Internal match(tripId, pickup) invoked by the trip service.
  • Driver app POST /offers/:offerId/accept and POST /offers/:offerId/decline.

Scalability

Each city shard handles its own requests, so dispatch scales with the number of cities. In dense cities, requests are collected in short windows of about 2 seconds and matched as a batch, minimizing total pickup time across all riders instead of greedily assigning each one.

Tradeoffs

Batch matching produces better global outcomes (fewer long pickups) at the cost of a small delay before the first offer. Pure greedy matching is faster but strands riders who request moments after a nearby driver was given to someone else.

Owns the lifecycle of each trip as a state machine.

  • A strongly consistent database such as PostgreSQL (sharded by city) or Spanner.

Data Model

  • trips(trip_id, rider_id, driver_id, city, status, pickup, dropoff, quote_id, fare, created_at, updated_at, version)
  • trip_events(trip_id, seq, event_type, payload, created_at) as an audit log.

Scalability

About 1,000 trip writes per second at peak is modest for a sharded relational database. Active trips are a small working set; completed trips are archived to cheaper storage after 30 days.

Tradeoffs

Each transition is a compare and set on status and version, so concurrent events (a driver cancels while the rider is being picked up) resolve deterministically. An eventually consistent store would be easier to scale but makes double assignment and double charging possible.

Computes travel time between points on the road network.

  • A routing engine using contraction hierarchies over map data, with live traffic speeds applied per road segment.

Scalability

Dispatch needs many short ETA queries per request (one per candidate driver), so the engine supports one-to-many queries and caches results for popular cell pairs for a minute.

Tradeoffs

Straight-line distance is fast but picks drivers across rivers or highways who are far away by road. Road ETAs cost more compute but noticeably cut real pickup times.

Calculates upfront prices and surge multipliers.

  • A stream job (Flink) counting open requests and available drivers per cell per minute; results in a key-value store.

Scalability

Surge is computed per cell every minute, then smoothed across neighbors so prices do not jump block to block. Quotes are stored with an expiry so the confirmed price is exactly what the rider saw.

Tradeoffs

Honoring the quoted price means the company absorbs the difference when a trip runs long, but riders strongly prefer certainty, and the quote protects against disputes. Surge is capped by policy in some cities.

Charges riders and pays drivers exactly once per trip.

  • An external payment processor for card charges; an internal double-entry ledger for driver earnings.

Scalability

Charges are processed asynchronously from a durable queue, so a processor slowdown does not block trip completion. Failed charges are retried with backoff and the trip id as the idempotency key.

Tradeoffs

Authorizing an estimated amount at request time and capturing the final fare later reduces failed payments after the ride. It adds a second processor call per trip, which is worth it to avoid unpaid rides.

Related questions