Skip to content

The finished design, decision by decision

How to design a Ride-Sharing Service (Uber-style)

Not the one correct diagram, but a design you can defend under these constraints: the finished architecture, then every stage's question with the reasoning that answers it, the tradeoffs it accepts, and where another engineer could land differently.

Matching riders and drivers across a busy city

The short answer

10 parts, each with one job. The map below shows how requests and data move between them; the stages after it explain why each part is there.

Rider app
Creates a ride request and receives versioned trip-state updates.
Driver app
Sends sequenced location updates and accepts or declines a specific offer.
Ride API
Authenticates requests, enforces per-client limits, and returns durable trip state.
Trip and assignment store
Owns trip state, active driver reservation, offer ID, and state version.
Outbox and match queue
Publishes committed trip-request events and retries match work by city.
Dispatch workers
Queries nearby candidates, checks eligibility, obtains route ETAs, and creates leased offers.
Live location index
Keeps the latest sequenced position, availability, cell, and expiry for each driver.
City-cell index
Maps a pickup to a cell and returns drivers in progressively wider neighboring cells.
Route ETA service
Ranks a small candidate set by road pickup time, not just distance.
Push and live updates
Delivers versioned offers and trip changes to rider and driver apps.
123456789101112CLIENTRider appCLIENTDriver appEDGERide APIDATABASETrip andassignment storeQUEUEOutbox andmatch queueWORKERDispatch workersCACHELive locationindexSERVICECity-cell indexEXTERNALRoute ETAserviceSERVICEPush andlive updates

Select a component to see what it is responsible for and which state it owns.

  1. 1Rider app → Ride API: Request a ride
  2. 2Ride API → Trip and assignment store: Create REQUESTED trip
  3. 3Trip and assignment store → Outbox and match queue: Commit match event
  4. 4Dispatch workers → Outbox and match queue: Claim city match work
  5. 5Dispatch workers → City-cell index: Search nearby cells
  6. 6City-cell index → Live location index: Fresh available drivers
  7. 7Dispatch workers → Route ETA service: Road ETA for candidates
  8. 8Dispatch workers → Trip and assignment store: Lease offer and reserve driver
  9. 9Dispatch workers → Push and live updates: Send versioned offer
  10. 10Driver app → Live location index: Sequenced location ping
  11. 11Driver app → Ride API: Accept or decline offer ID
  12. 12Push and live updates → Rider app: Trip status version
  • Request / response
  • Asynchronous
  • Server push

Why does this design work?

The design separates three kinds of state that have different correctness needs. Driver location is a high-rate, expiring hint in a spatial index. A ride and its assignment are durable, versioned state transitions. Notifications are retriable messages that carry the IDs and versions of those transitions. A grid reduces the search space; route ETA ranks the remaining candidates; a conditional reservation decides who actually gets the trip. This lets the system tolerate delayed telemetry, retries, and local demand spikes without pretending that a map pin or push receipt is an assignment.

Invariants, and where they are enforced

  • A driver has at most one active assignment.

    A conditional state transition reserves the driver for one offer ID; competing matches lose the compare-and-set and must try another candidate. Enforced by Trip and assignment store, Ride API.

  • A ride has at most one accepted driver.

    Accept changes the trip from OFFERED to ACCEPTED only when the offer ID and state version still match; duplicate or late replies are no-ops. Enforced by Trip and assignment store, Ride API.

  • Dispatch never offers a trip using an expired driver position.

    Each ping carries a monotonic sequence and event time; the index expires entries after ten seconds, and the matcher checks freshness again before creating an offer. Enforced by Live location index, Dispatch workers.

  • A delayed notification cannot move a trip backwards.

    Messages carry the committed trip version and offer ID; clients ignore updates older than the last state they applied. Enforced by Trip and assignment store, Push and live updates.

What does it rely on?

  • Driver apps send sufficiently fresh, sequenced location updates and can tolerate occasional discarded pings.
  • The assignment store supports atomic conditional state transitions or transactions for the ride and driver reservation.
  • The routing service can return useful estimates for a bounded candidate batch within the request's latency budget.
  • The cell index can widen or split searches without losing updates at cell boundaries.

What tradeoffs does it make?

ChoiceGainsCosts
Keep only the current driver position in the matcher indexFast writes, bounded storage, and cheap freshness checks.A separate, explicitly retained history is needed for trip replay, safety, or audit.
Reserve before notifyingA push retry cannot make one driver appear available for two trips.A slow or lost notification holds a short lease and needs expiry and cleanup.
Progressively expand nearby cellsFast common-case lookup without arbitrarily missing drivers at a cell edge.Sparse areas can require additional queries and exceed the first-offer budget.

What are the reasonable alternatives?

Geospatial index in a relational database
Better when one region and moderate update volume make operational simplicity more valuable than a specialized write path.
Geohash or R-tree partitions
Better when the chosen database already supports these indexes well, and variable cell density or rectangle queries fit the workload.
H3-style hierarchical hexagonal cells
Better when consistent global cells and hierarchical aggregation are useful across many cities and spatial analytics.
Sequential offers to the top candidate
Better when driver acceptance is high and minimizing duplicate offers matters more than shaving the first-offer latency.
Small parallel offer batch
Better when acceptance is slow or reliability targets require parallelism, and conditional reservations can release losing drivers quickly.

When does it stop working?

  • GPS is inaccurate or spoofed; use plausibility checks, device integrity signals, and driver-side safeguards before treating a point as truth.
  • A single metro area exceeds one cell or database shard; split by city and subregion while keeping trip ownership stable.
  • The routing service is unavailable long enough that no trustworthy pickup ETA can be returned; matching must expose a degraded or pending state.
  • Drivers can accept multiple ride types or pooled trips; the one-active-trip invariant must become a capacity and route-compatibility model.
  • Regulation or safety policy requires durable location history; move that history to a separately retained, access-controlled stream rather than the hot matching index.

Every stage, decided and explained

Spoilers, for the whole investigation: each stage's question and its answer, the reasoning behind it, and the tradeoffs it accepts. If you have not worked through the stages yet, you may want to do that first.

Work through the stages

Stage 1 of 5 · Model

The location stream is the first bottleneck

Start with the data that arrives whether or not anyone requests a ride. There are 60,000 online drivers, and every available driver sends one ping every three seconds. Ride requests average 2,000 a minute and briefly peak at five times that rate.

What you need to know first

For a periodic stream, divide the number of active senders by the reporting interval. Do not size a system only for the user action that looks most important: background location updates can outnumber ride requests by orders of magnitude.

60,000 drivers each send one location ping every 3 seconds. About how many pings per second reach the service?

About 20,000 pings/second.

60,000 ÷ 3 = 20,000 pings a second. Peak ride requests are 2,000 × 5 ÷ 60 ≈ 167 a second. Location updates are about 120 times as frequent, so they need a cheap, short-lived write path separate from durable trip history.

What the stage asks

What would you store for each location ping, and which location data would you persist durably? Explain the freshness and ordering rules.

Reference answer

Keep the latest position, availability, cell, sequence number, and received time in an expiring index. Persist trip transitions and the location samples required for an active trip or audit separately. A delayed ping with a lower sequence must not move a driver backwards. The raw location stream is high-volume and short-lived; the trip lifecycle is comparatively small and durable.

What a strong answer covers

  • Keeps only each driver's latest position in the fast matching index rather than appending every ping as durable trip data.
  • Expires or rejects locations older than ten seconds.
  • Uses a sequence number or timestamp so a delayed older ping cannot overwrite a newer position.

The reasoning

  1. 20,000 location pings/second versus about 167 peak ride requests/second.
  2. The live matching index and durable trip history have different retention and write requirements.
  3. Freshness and event order are correctness rules, not cache-tuning details.

The location path dominates request count, so putting every ping into the same durable relational write path as trips couples cheap refreshes to expensive lifecycle guarantees. Keep a compact current-position index with a short TTL and a monotonic sequence. Durable storage owns the request, offer, acceptance, pickup, completion, and cancellation transitions. If a location is too old or out of order, discard it rather than making a confident match from stale data.

Where another engineer could land differently

Persisting a full location history can be appropriate for safety, billing, or regulation, but stream it to separate partitioned storage with an explicit retention policy; do not make the matcher scan that history.

Stage 2 of 5 · Decide

Find candidates, then ask the road network

A city has tens of thousands of eligible drivers, but a ride request should not call the routing service for all of them. A spatial index needs to reduce the candidate set quickly without hiding a nearby driver just across a cell boundary.

Uber has publicly described using H3, a hierarchical hexagonal grid, for marketplace analysis. It is one possible cell system, not a requirement for this design. See the [Uber H3 engineering post](https://www.uber.com/gb/en/blog/h3/).

What you need to know first

A spatial grid maps a coordinate to a cell. Query the pickup cell and its neighbors, reject unavailable or stale drivers, and expand outward if the candidate set is too small. Cell distance is a coarse filter: lakes, bridges, and one-way roads mean the closest point may not have the shortest pickup time.

What the stage asks

How should the matcher find the first driver offer under the one-second budget?

  1. Sound

    Search the pickup cell and nearby cells, filter for fresh available drivers, then rank a small set by route ETA.

    This uses the grid to bound work and the road network for the decision that matters: pickup time. Expand the cell radius if too few eligible candidates remain.

  2. Flawed

    Ask the routing service for a route from the pickup to every online driver, then sort the results.

    A routing call per driver makes the request fan out across tens of thousands of expensive calls and cannot meet the latency budget.

  3. Flawed

    Only consider drivers inside the exact pickup cell and send to the closest coordinate.

    Cell boundaries can exclude a driver physically closer across the edge, and coordinate distance does not account for roads, barriers, or traffic.

  4. Defensible

    Query one fixed radius around the pickup and return no match if it is empty.

    A strict service-area radius is simple and can be right for a product with a hard pickup limit. Under this prompt it will miss available drivers just outside the cutoff; retry with a larger radius or adjacent cells before reporting no match.

What a strong answer covers

  • The cell or radius lookup bounds the number of candidates.
  • Route ETA, rather than straight-line distance, decides pickup order.
  • Search widens when the first neighborhood has too few eligible drivers.

The reasoning

  1. A spatial cell is a candidate index, not a guarantee of nearest-by-road ordering.
  2. Filter stale, occupied, and ineligible drivers before expensive routing calls.
  3. Progressive expansion avoids both an unbounded city scan and an arbitrary no-match boundary.

The matcher first gets a bounded candidate set from the pickup cell and neighboring cells. It filters by availability, TTL, vehicle constraints, and current reservations, then requests route ETAs for a small batch. It offers in ETA order, or in a carefully chosen small parallel batch if a sequential offer would exceed the latency budget. The cell resolution and expansion policy are tuning decisions; measure empty searches, candidate count, ETA error, and dispatch latency by area before changing them.

Tradeoffs

ChoiceGainsCosts
Smaller cellsFewer candidate drivers per lookup and less routing work.More cells to query near boundaries and greater sensitivity to map density.
Larger cellsFewer index operations and a wider candidate pool in sparse areas.More candidates to filter and route, especially in dense downtowns.

Where another engineer could land differently

A geospatial database query with a distance index can replace a cell service at moderate scale. The design still needs a freshness cutoff and route-aware ranking; the data structure is an implementation choice.

Stage 3 of 5 · Decide

Reserve the driver before sending the offer

Two match workers can process different ride requests at the same time and both see the same driver as available. A push provider can also retry an offer after a timeout, even though the first copy was delivered.

What you need to know first

A read followed by a write is not an exclusive claim. The reservation must be atomic with the state transition that creates the offer. Give each offer an ID and expiry, and make a driver's reply conditional on that exact ID and current trip version.

What the stage asks

Describe the atomic state change that makes it impossible for two trips to claim the same driver. What should happen if the notification times out after the database commit?

Reference answer

Use a transaction or conditional writes to reserve the driver and create the offer against the trip's current state. Persist an offer ID, expiry, and trip version before sending push. If the provider times out, retry delivery with the same ID. Duplicate accepts become no-ops; a late accept after expiry fails the state/version check. The offer lease is released only by a conditional transition that still refers to the same offer, so an old cleanup task cannot release a newer assignment.

What a strong answer covers

  • Reserves only if the driver is still AVAILABLE, atomically changing it to RESERVED for one offer ID.
  • Accepts only if the trip is still OFFERED for that offer ID and version.
  • Retries notification delivery with the same offer ID; a timeout does not create a second reservation.
  • Expires an unaccepted reservation and reopens matching after a bounded offer window.

The reasoning

  1. Never trust an availability read as an assignment lock.
  2. Commit the offer identity and reservation before external notification.
  3. A timeout means the delivery result is unknown; retry with the same idempotency key.

The durable store is the authority for assignment. The matcher uses an atomic conditional update from AVAILABLE to RESERVED and ties it to the ride and offer ID. The rider sees an offer only after this state commits. A driver accept conditionally advances that offer to ACCEPTED; a second worker or duplicate notification cannot create another accepted driver. When the offer expires, a compare-and-set checks the same offer ID before releasing the reservation.

Tradeoffs

ChoiceGainsCosts
One database transaction for trip and driverA simple, explicit invariant for a city-level assignment store.The transaction path may need careful partitioning and can contend on popular drivers.
Separate trip and driver stores with a sagaIndependent scaling and ownership of each data set.Intermediate states and compensating release become part of correctness.

Where another engineer could land differently

A market can send an offer to a small group at once to reduce wait time. Each candidate still needs a unique reservation, and the first valid acceptance wins through one conditional trip transition; losing reservations are released.

Stage 4 of 5 · Break it

A delayed location or offer cannot undo newer state

Mobile networks reconnect and replay work. Arrival order is not event order, so both the live-location record and the trip state need a rule that makes stale messages harmless.

What you need to know first

Keep a monotonic sequence on driver pings and a version on each trip transition. A consumer applies an event only if it advances the stored value. This works only when the compare and update are atomic; checking the version in application code and writing later reopens the race.

What the stage asks

Which statements remain true when messages arrive late or more than once?

  1. Holds

    A location ping with sequence 79 cannot overwrite the stored position at sequence 82.

    The index updates only when the incoming sequence is newer, using an atomic conditional write. The old ping is acknowledged or discarded without changing the current location.

  2. Holds

    Retrying the same driver's accept request must return the same accepted-trip result.

    The offer ID and trip version make the operation idempotent. A repeat can report the already committed result but cannot create another assignment.

  3. Fails

    If a driver accepts an offer after its lease expired, the API should accept it because the push arrived late.

    The offer expiry and ID are part of the compare-and-set. A late reply is rejected, and the app receives the current trip state so it can refresh rather than create a conflicting assignment.

  4. Fails

    Push delivery order is enough to keep the rider's screen correct.

    Push can arrive out of order. Each update needs a trip version; clients ignore versions older than the latest one they applied and can refetch durable state after a gap.

The reasoning

  1. Sequence numbers protect the current location from replayed older pings.
  2. Offer IDs and versions make retries safe and expired responses rejectable.
  3. The server's durable trip state is authoritative; push is a delivery channel.

The location index accepts a position only when its sequence exceeds the stored sequence and its age is within the TTL. The trip API makes every state transition conditional on the expected version and offer ID. If an old push arrives, the client ignores its lower version and fetches the current trip. If an accept arrives after the lease was replaced, the server rejects it and returns current state. Do not use wall-clock arrival time alone to order events from mobile clients.

Stage 5 of 5 · Change it

An event lets out and one cell becomes hot

The city average hides local hotspots. A single popular cell can overload a partition, while increasing every worker's concurrency can send even more traffic to an already slow routing dependency.

What the stage asks

How do you keep matching responsive at the stadium without letting a routing slowdown or one hot cell take down the whole city?

Reference answer

Partition current locations and match work by city, then by spatial cell or a bounded set of adjacent cells. Split hot cells more finely or use a per-cell queue and concurrency budget; do not put every city behind one global queue. Limit route lookups per match, use short timeouts and a circuit breaker, and avoid retry storms. If exact ETAs are unavailable, use a clearly labeled coarse estimate or keep the request pending; never report a driver as assigned until the assignment transaction commits. Alert on per-cell queue age, candidate count, stale-location fraction, routing latency, offer acceptance, and unassigned-request rate.

What a strong answer covers

  • Partitions or shards work by city and cell, and hot cells can split or use finer cells without relocating all city state.
  • Caps concurrent and per-request routing calls, with bounded retries and a timeout budget.
  • Widen or defer candidate search and communicate delayed matching instead of falsely marking a ride assigned.
  • Measures dispatch latency, empty candidate sets, stale pings, cell skew, and routing error rate by area.

The reasoning

  1. Partition by where the work happens, and identify hot cells rather than relying on city averages.
  2. A slow routing dependency needs a concurrency budget and a bounded fallback, not unlimited retries.
  3. A delayed match is an honest state; an uncommitted assignment is not an assigned ride.

Local queues and spatial shards isolate a stadium surge from the rest of the city. If one cell still dominates, subdivide it or split its workers and index while preserving a stable mapping for updates. Bound routing calls so a slow dependency does not accumulate an unbounded queue. The product can keep the rider informed that matching is delayed, then retry with a wider search or lower-fidelity ETA when safe. Track tail latency and match outcomes by cell to find the hotspot before a citywide dashboard does.

Tradeoffs

ChoiceGainsCosts
Fine cells and local queuesBetter isolation and more control over hot areas.More partitions, boundary queries, and operational state to manage.
Coarse cellsSimpler index and fewer workers at ordinary traffic.A popular venue can become one hot key and contaminate nearby requests.

How you did

Now try it as an interview question

  • “Design Uber or Lyft's ride-sharing dispatch system.”
  • “Find the nearest available driver to a pickup under a one-second latency target.”
  • “Design a real-time driver-location service for multiple cities.”
  • “Match couriers to delivery orders while preventing duplicate assignments.”
  • “Handle a stadium event that creates a sudden, localized demand spike.”

The timed session in Review mixes stages from this and other systems with concept recall and questions about your own projects.

Back to the last stage