Ride-Sharing Dispatch, Actually Explained

Finding the nearest available driver sounds like a database query. It isn't — not at real scale. A ride-sharing platform is a two-sided marketplace where both sides are moving and arriving continuously, and a match that's locally correct for one rider can still be a worse outcome for the city. Run the three simulators below and watch a greedy match lose to a batch, a race condition double-book a driver, and a naive price loop spiral before it settles.

Rahul Bisht·
Jul 11, 2026
·System Design·
1 min read
Interactive Guide

Three live simulators — watch a match get optimized, a race get resolved, and a price loop get tamed

01 · Ground Zero

What Even Is a Ride-Sharing Dispatch System?

A ride-sharing dispatch system's whole job is simple to say: match someone who wants a ride to someone nearby who can give them one, fast, and at a price that reflects supply and demand right now. That's the entire promise.

Everything below this point is about honoring that promise once both sides of the match — riders and drivers — are moving, arriving continuously, and numerous enough that a match that's locally correct for one rider can still be a globally worse outcome for the city. The query framing (“find the nearest driver”) is the wrong mental model from the start, and the rest of this guide is about why.

02 · Scope It First

What Are We Actually Building?

Before reaching for a geospatial index, it's worth being explicit about what this system is actually on the hook for — the same way you'd scope it out loud in an interview before drawing a single box.

Functional requirements

  • Match a ride request to a nearby, available driver
  • Keep driver location updated in near-real time
  • Show an estimated price and ETA before the rider confirms
  • Adjust pricing during high local demand

Explicitly out of scope: payment processing, turn-by-turn navigation, and driver onboarding — each is a separate system this one calls or hands off to.

Non-functional requirements

  • Latency: a match found within a few seconds, even at peak
  • Availability over consistency for location: a few seconds of staleness is fine, an outage isn't
  • Strict consistency for exactly one thing: reserving a driver — a double-booking is a real, user-facing failure
  • Horizontal scale by geography: load in one city can't degrade matching in another

That consistency split — loose for location, strict for reservation — is the single decision everything below inherits.

03 · Size It

Sizing the Problem

Numbers here are explicit back-of-envelope estimates, not measured production values — the point is to find the one number that actually constrains the design, not to be precise.

Traffic (ride requests)

~10 million rides/day globally averages to only ~115 requests/second — peaks maybe 5x that around commute hours and big events. Low enough that ride requests are never the bottleneck.

Live location writes

~1M concurrent drivers, each updating every ~4 seconds ≈ 250,000 writes/second platform-wide, ~25MB/second raw at ~100 bytes/update.

Index size isn't the problem

1M drivers × ~100 bytes/entry (geohash + id + timestamp) is only ~100MB total — trivially small, fits in one machine's RAM. Memory was never the constraint.

The constraint that actually rules the design

250,000 writes/second is the real number. A single Redis-class node tops out on the order of 100–200K ops/second for geo-indexed writes — meaning one node can't absorb this alone. The index has to be sharded, and that sharding — not the ~100MB of data — is the design problem worth spending interview time on.
04 · The Whole Picture

The Shape of the System

Before drilling into any one piece, here's the system end-to-end: a rider's request reaches the matching service, which searches a geospatial index kept fresh by a constant stream of driver location updates, and returns a matched driver. The API contract and data model below are what every later section refines — nothing past this point replaces this picture, it only explains why each piece of it exists.

ride request
match request
nearby drivers
matched driver
location, every few sec
update index
Rider
Ride Service
Matching Servicebatches ~2-3s/region
Geospatial Indexdriver locations
Location Ingestion
Driver Applocation every few sec

Location ingestion keeps the index fresh independently of matching — hover a node

The “Geospatial Index” box is a cluster, not one node. Driver locations are keyed by a geohash prefix — length 6 works out to roughly 1.2km × 0.6km cells, small enough for a useful “nearby” search — and each prefix is assigned to a shard by consistent hashing, so a lookup for one neighborhood only touches one or two shards. That's the piece that actually absorbs the ~250,000 writes/second from the capacity estimate; no single node has to.

// request_id is client-generated and idempotent — a retried request never creates a second ride
POST /v1/rides/request
{ request_id, rider_id, pickup: {lat, lng}, dropoff: {lat, lng} }
→ { ride_id, status: "matching" }
// sent by the driver app every few seconds
POST /v1/drivers/{id}/location
{ lat, lng, timestamp }
GET /v1/rides/{id}
→ { status, driver_id, eta_seconds }

Rider

The person requesting a ride.

Driver

The person fulfilling it — a mostly-static profile plus a constantly-changing current location.

Ride

The request itself, moving through a state machine: requested → matched → in progress → completed/cancelled.

DriverLocation / Zone

A driver's current geo-position, updated every few seconds; Zone is the neighborhood-sized cell used to compute local supply/demand and surge pricing.
requests
fulfills
currently at
priced within
Rider
Driver
Ridestate machine
DriverLocation
Zone

DriverLocation is deliberately not a field on Driver — different write pattern, different store

Three different write patterns, three different stores. DriverLocation is overwritten every few seconds for a million drivers — the sharded in-memory geo index from above, TTL'd, no durability needed since a stale entry just gets overwritten next update. Ride is the opposite: infrequent, high-value state transitions that need ACID guarantees, so it lives in a relational store where every transition is kept as an immutable event — support and disputes need to reconstruct exactly what happened to a specific ride. Zone is small, changes rarely, and is cheap to cache in full on every matching-service instance.

05 · Deep Dive: The Naive Query

The Query You'd Reach For First

Naive
On every ride request: scan the full driver table, compute the distance from each driver to the pickup point, pick the closest available one. It's the query anyone reaches for first — simple, obviously correct.
Breaks
It's an O(n) scan against the entire fleet, on every single request. With a million drivers, that computation — repeated again for the next request a moment later — doesn't come close to a multi-second matching budget, and it only gets worse as the fleet grows.
Fix
Index driver locations geospatially, so a request only searches a bounded area, not the whole fleet. Two structures actually get used: a geohash grid — fixed-size cells, lat/lng encoded as a short base32 string, lookup by prefix — is the simplest to build and what Redis's GEOADD/GEOSEARCH use under the hood, but a driver just across a cell boundary is invisible unless you also check the 8 neighboring cells. A quadtree — cells that shrink where drivers are dense and stay large where they're sparse — handles uneven density (downtown vs. suburbs) better, at the cost of a mutable tree instead of a flat key lookup. Either way, a request that used to touch a million rows now touches a handful of cells.
Tradeoff
This makes a single match fast — it doesn't touch the deeper problem underneath: matching one request at a time, greedily, still isn't the same as matching well. That's the subject of the next section.
06 · Deep Dive: The Payoff

Matching in Batches, Not One Request at a Time

Naive
Match every request the instant it arrives, to whichever available driver is closest right now.
Breaks
At real scale — a stadium letting out, a big event ending — many requests land within the same second in the same area. Assigning the closest driver to whichever request happened to ask first isn't necessarily the assignment that minimizes total wait time across all of them, as the simulator below shows directly.
Fix
Collect ride requests and available drivers within a short window (a couple of seconds) for a given region, and solve the assignment across all of them jointly — this is a bipartite matching problem, not a sequence of independent nearest-neighbor lookups.
Tradeoff
A small, deliberate delay before a match is confirmed, in exchange for a better aggregate outcome. Partitioning the world into regions (roughly by city or geohash cell) keeps each batch's problem size bounded, so the whole system still scales horizontally, region by region.
Try it

A constructed scenario, not measured data: Rider 1 and Rider 2 both want Driver 1 — Rider 1 would be almost as happy with Driver 2, but Driver 2 is a bad match for Rider 2. Watch what each strategy does with that.

Drivers
D1 · freeD2 · freeD3 · freeD4 · free
Matches

Press “Send 4 requests” to run the scenario.

Total distance across all matches: best possible total for this scenario: 12 min

The simulator solves its 4-rider batch exactly, by brute force, to make the point clearly. Real batches during a surge can have thousands of riders and drivers in one region, and the exact solution (the Hungarian algorithm, O(n³)) gets too slow to run inside a 2-3 second window at that size — production systems fall back to a faster approximate assignment (greedy on sorted distances, or a bounded local-search heuristic) that captures most of the benefit without the cubic cost.

07 · Deep Dive: The Correctness Bug

The Double-Booking Race

Naive
Reserve a driver by reading their status, checking it's available, then writing reserved — a read followed by a separate write.
Breaks
Two nearly-simultaneous requests can both query the index, both see the same driver as the closest available option, and both attempt to confirm a match — a real race condition, not a hypothetical one, as the simulator below shows.
Fix
Make “reserve this driver” a single atomic operation — a compare-and-swap on the driver's status from available to pending-match, not a read followed by a separate write. In practice that's a small Lua script run inside Redis: check the status field on the driver's index entry and flip it to pending-match in one round trip, so there's no gap between the check and the write for a second request to land in.
Tradeoff
The losing request doesn't just fail — it needs to fall through cleanly to its next-best available driver, not surface an error to the rider.
Try it

Both riders' requests hit the geospatial index within milliseconds of each other and both land on Driver Alex as the closest available match.

Request A
waiting
Request B
waiting
08 · Deep Dive: The Feedback Loop

How Surge Pricing Can Turn Unstable

Naive
Compute the surge multiplier straight from the current ratio of open ride requests to available drivers in a zone, and recalculate it as often and as sharply as that ratio changes.
Breaks
If price recalculates too frequently or too sharply in response to short-lived imbalances, it can oscillate: a price spike suppresses demand and pulls in more drivers, which then over-corrects the price back down — and the cycle repeats. The simulator below runs the actual feedback loop and shows it happening.
Fix
Smooth the input with an exponentially weighted moving average (EWMA) of the ratio instead of an instantaneous snapshot, and separately rate-limit how much the multiplier can move per tick — smoothing the input and capping the output are two different fixes, and the simulator needs both to actually stop overshooting.
Tradeoff
A smoothed, rate-limited price responds more slowly to a genuine, sustained demand spike — an explicit trade of responsiveness for stability, not a free lunch.
Try it

An event just ended: demand jumps well above nearby supply at tick 0. Each bar is one recalculation tick — watch how the multiplier responds to drivers reacting to last tick's price.

1.0x
Price ranged from to across the run
09 · Operate It: When It Breaks

When a Node, a Batch, or a Region Fails

Geospatial index node loss

Impact: Drivers indexed on that node briefly disappear from searchResponse: Every driver republishes their location every few seconds, so the index is self-healing — a lost node's data reappears within one ingestion cycle. Real replication still backs the read side, but the write side tolerates loss gracefully by design

Matching service crash mid-batch

Impact: A pending batch of requests and drivers needs to be re-runResponse: Matching is stateless per batch — a crash just means the next instance re-collects whatever requests are still open and re-runs the assignment; no ride is silently dropped, since requests stay in the requested state until matched

Ride state store failure

Impact: Risk of losing an in-progress ride's statusResponse: Unlike location data, Ride records are durable, low-volume, high-value writes — replicated synchronously across availability zones, since losing an in-progress ride is a real customer-facing incident, not a recoverable blip

Regional partition isolated from the rest

Impact: A city's matching temporarily can't borrow drivers from neighboring regionsResponse: Acceptable in the short term, since ride-sharing supply and demand are inherently local — a region operating alone for a few minutes rarely degrades match quality meaningfully

The pattern mirrors the data model split: ephemeral, self-healing location data can tolerate loss because it's constantly being refreshed anyway; durable ride state cannot, and is engineered accordingly.

10 · Operate It: Watching It

What to Watch, and Where Trust Breaks Down

Time-to-match (P50/P99)

Why it matters: The core promise to the riderAlert on: Sustained rise vs. baseline for a region

Location ingestion lag

Why it matters: Stale locations directly degrade match qualityAlert on: Lag exceeding a few seconds at the geospatial index

Match-then-cancel rate

Why it matters: A high rate often means the matched driver was further away than expected — stale or sparse location dataAlert on: Spike vs. regional baseline

Surge price volatility, per zone

Why it matters: Flags an unstable feedback loop before riders notice erratic pricingAlert on: Price flips beyond a set bound within a short window

Driver-submitted location is a self-reported signal, which means it can be spoofed — a driver claiming to be in a high-surge zone they're not actually in is a real abuse pattern, worth cross-checking against plausible movement speed between consecutive updates. Location data is also sensitive personal information for both riders and drivers; access to raw location history should be scoped tightly, and retention of precise historical pings minimized once a ride is complete rather than kept indefinitely by default.

11 · Judging an Answer

Judging the Answer: Mid, Senior, Staff

Mid-level

Gets to a geospatial index (geohash or quadtree) for nearby-driver lookup and a basic ride state machine, with some prompting toward why a full table scan doesn't work.

Senior

Independently identifies the greedy-matching-at-scale problem and proposes batched/windowed matching; correctly separates ephemeral location data from durable ride records and gives each a different store; catches the double-booking race condition and proposes an atomic compare-and-swap reservation, not just "add a lock."

Staff+

Frames matching explicitly as a bipartite assignment problem and reasons about its complexity — knows exact solving doesn't scale to a real batch size and names the approximate alternative; proposes consistent-hashing the geospatial index by region and ties that directly back to the write-throughput number, not just data size; reasons about surge pricing as a feedback system that can become unstable, not just a pricing formula.
12 · Go Deeper

Further Reading

Related posts

View all →