Design a Ride-Matching Engine — the walkthrough in full
A written version of the interactive walkthrough above — the same steps, decisions and trade-offs, laid out for reading, reference and search.
The big idea
What is a matching engine, specifically?
Not the whole ride-hailing product — just the one hard question buried inside it: given a rider who just requested a ride and a hundred available drivers scattered across the city, WHICH driver should be assigned? Pick wrong and you've either sent a driver 20 minutes away when a 3-minute driver existed, or you've locally optimized one match while leaving the rest of the marketplace worse off.
We'll build the matching pipeline specifically: how "nearby" is actually measured, why matching one rider at a time is provably worse than matching a whole window together, how surge pricing balances a two-sided marketplace, and how fairness keeps the system healthy for drivers, not just efficient for any one rider.
How to read this: Each step opens with a real design decision — make the call before I show you what ships. Watch the diagram grow, hover the boxes, and at the end kill the geo index and flood a demand spike to see what degrades and what absorbs it. Hit Begin.
Step 1 · The baseline
Nearest driver, by straight-line distance
Simplest matching: find the geographically closest available driver (straight-line distance on a map) and assign them immediately, one rider at a time. What goes wrong?
Design decision: Matching by straight-line distance, one rider at a time, immediately. What's the core failure?
The call: Straight-line distance ignores the real road network (a driver could be geographically close but 20 minutes away by actual roads), and matching greedily one rider at a time can lock in a locally-good pair that's globally worse than a different assignment across all pending riders and drivers. — Two separate flaws stack here: the DISTANCE METRIC is wrong (straight-line ≠ real travel time across a road network with barriers), and the ALGORITHM is short-sighted (assigning the first rider to their nearest driver might use up a driver who was actually the BEST match for a rider arriving a second later, while a different driver would have served the first rider almost as well).
Two separate problems: the distance metric is wrong (straight-line ignores rivers, highways, one-way streets — real travel time can differ wildly from map distance), and the algorithm is short-sighted (matching greedily, one rider at a time, can lock in an assignment that's locally fine but globally worse than considering multiple pending riders and drivers together).
Two axes of "wrong": A matching system can be wrong about WHAT it's optimizing (the cost function — distance vs. real ETA) and wrong about HOW it optimizes (greedy vs. a proper assignment algorithm). Both need fixing, and they're independent — fixing one doesn't fix the other.
Step 2 · Real ETA, not map distance
Route, don't just measure
Fix the metric: use actual predicted travel time instead of straight-line distance.
Introduce an ETA / Routing service that computes real road-network travel time between a candidate driver and the rider — accounting for actual streets, current traffic conditions, and turn restrictions. This becomes the true cost function for matching: rank candidate drivers by predicted pickup ETA, not by distance on a map.
Optimize for the metric that matters to the user: The rider experiences WAIT TIME, not distance — optimizing for the wrong proxy (distance) can produce visibly worse outcomes (a driver stuck behind a river crossing) even when the "closest" driver was technically correct by that flawed metric.
Step 3 · Surge pricing
When demand outstrips nearby supply
A concert lets out: 500 ride requests appear in six square blocks with 40 available drivers. Matching can't manufacture drivers that don't exist. What should the system do?
Design decision: Demand vastly exceeds nearby driver supply. What can the system actually do about it?
The call: Raise the price dynamically in the affected area (surge pricing) — this both rations demand (some riders will wait or find alternatives at a higher price) and creates an incentive for nearby off-duty or distant drivers to head toward the surge zone, increasing supply. — Surge is a two-sided lever: it moderates demand (price-sensitive riders wait it out) AND grows supply (higher fares pull drivers who'd otherwise be elsewhere or offline). It's a real-time price signal doing double duty in a two-sided marketplace with a genuine, temporary supply shortage.
Introduce surge pricing: when a geographic area's demand meaningfully exceeds nearby supply, raise the price for that area in near-real-time. This is a genuine market-clearing mechanism, not just a revenue lever — it rations demand toward those who value the ride most right now, and pulls additional driver supply toward the shortage.
A two-sided marketplace needs a market-clearing signal: Matching alone can't fix a supply shortage — there's no assignment algorithm that conjures drivers into existence. Price is the mechanism that actually changes the underlying supply and demand quantities, not just how they're allocated.
Step 4 · Batch, don't match greedily
Solve the whole board, not one move at a time
Even with correct ETAs, matching riders the instant each request arrives — one at a time, in whatever order they happen to land — can produce a worse OVERALL assignment than considering several pending riders and drivers together. How do you match better than greedy, without making every rider wait forever for a "perfect" global solve?
Design decision: Greedy one-at-a-time matching is provably suboptimal. What's the practical fix?
The call: Collect pending ride requests and available drivers over a short window (a few seconds), then solve an assignment-optimization problem across that whole batch at once, minimizing total wait/ETA across all of them together — then repeat for the next window. — A short batching window (seconds, not minutes) barely delays any individual rider, but gives the matcher visibility into MULTIPLE riders and drivers simultaneously, so it can solve a proper assignment problem (conceptually similar to the Hungarian algorithm for bipartite matching) that minimizes total cost across the group — routinely better than whatever a greedy, one-at-a-time pass would have produced, for a negligible latency cost.
Introduce a Batch Matcher: collect pending requests and available drivers over a short window, then solve matching as an assignment-optimization problem across the whole batch — minimizing total cost (aggregate ETA) rather than optimizing each pair in isolation. A few seconds of batching buys a meaningfully better global outcome for a cost riders barely notice.
Greedy vs. globally optimal: This is the classic assignment-problem trade-off: greedy algorithms are fast and simple but can be provably worse than an optimal solve over the same inputs. Batching trades a small, bounded delay for visibility into more of the problem at once — the same principle behind batch-processing trade-offs across many systems, applied here to real-time dispatch.
Step 5 · Ingesting driver locations at scale
A write-heavy problem, not a read-heavy one
Every online driver pings their location every few seconds. At real scale, that's an enormous, continuous write volume hitting the geo index — a very different load pattern than the mostly-read search traffic other systems deal with. What does that demand from the index?
The Driver Geo Index has to be optimized for high write throughput — frequent location updates from potentially millions of concurrently-online drivers — while still serving fast "who's nearby" spatial queries. In practice this means partitioning the index geographically (so writes and reads for one city's drivers don't contend with another's), keeping each update lightweight (overwrite last-known-location, not append an unbounded history), and treating slightly-stale location data as acceptable — a driver's last known ping from 3 seconds ago is a fine approximation of where they are now.
Match the system to its actual read/write ratio: Unlike hotel search or dating-app candidate generation (overwhelmingly read-heavy), driver location ingestion is a genuinely write-heavy workload. Recognizing that and partitioning/optimizing accordingly — rather than reusing a read-optimized design pattern by default — is itself a key design decision.
Step 6 · Cancellations and fast re-matching
A driver cancels — now what?
A driver accepts a match, then cancels (traffic, changed their mind, a better offer). The rider is now waiting with no driver at all.
Detect the cancellation immediately and re-queue the rider into the next matching window with priority (they've already waited once) rather than making them submit a fresh request. The freed driver simultaneously becomes available again for the same or the next window — cancellations are just another input to the ongoing batch-matching loop, not a special, separately-handled case.
Treat failure as a normal input, not an exception path: A robust matching system doesn't need a bespoke "handle a cancellation" subsystem bolted on — if cancellation just means "this rider re-enters the pool with elevated priority, this driver re-enters the pool as available," the same batch-matching machinery already handles it correctly.
Step 7 · Fairness for drivers
Don't always pick the single best driver
Pure cost-minimization always routes requests toward whichever driver has the best ETA. Over time, that concentrates rides (and earnings) on drivers who happen to sit in high-density pockets, while others in lower-demand areas stay idle far longer. Is "always pick the mathematically best match" actually the right objective?
Design decision: Pure ETA-minimization concentrates rides on a few well-positioned drivers. Is that the right objective for the marketplace?
The call: No — blend the ETA cost with a fairness/idle-time factor in the matching objective, so a slightly-farther driver who's been idle much longer occasionally wins over the marginally-closer driver who just finished a trip. — Adding an idle-time factor to the cost function is a deliberate, small trade of pure efficiency for marketplace health: riders experience a negligibly different wait on average, while driver earnings and idle time distribute more evenly, keeping more drivers engaged and online long-term — which ultimately benefits riders too through better overall supply.
Blend the matching cost function with an idle-time fairness factor, tracked in an Idle-Time Ledger per driver: a driver who's been waiting longer gets a small cost-reduction bonus in the matcher's objective, so they occasionally win a match over a marginally-closer driver who just dropped off a rider. This is a deliberate, bounded trade of pure per-ride efficiency for long-run marketplace health.
The objective function encodes your priorities: "Minimize rider wait time" and "maintain a healthy, evenly-utilized driver base" are both real goals, and a pure single-objective optimizer can only serve one. Blending a fairness term into the same cost function the batch matcher already solves is how you serve both, in one system, without a separate fairness subsystem.
Step 8 · The sharp edges
Surge floods, GPS drift, and no-shows
Real-world stress tests: a sudden massive demand spike (handled by batching, from Step 4), noisy or drifting GPS signal producing bad ETA estimates, and a matched driver who never actually shows up.
GPS drift is dampened by smoothing location updates (discard implausible jumps, weight recent pings) before they feed the geo index, so a single noisy ping doesn't send a driver on a phantom route. A driver no-show is detected by a timeout on pickup confirmation, which immediately triggers the same re-matching path as an explicit cancellation from Step 6 — the rider is re-queued with priority rather than left stranded indefinitely.
Design for the unhappy path: Demand spike → batching absorbs it. Bad GPS → smoothing before it reaches matching. No-show → treated identically to a cancellation, reusing the same re-queue machinery. A matcher that only works when every driver's GPS is perfect and every acceptance is honored is a demo; one that degrades gracefully under real-world noise is a product.
You did it
You just designed a ride-matching engine.
- S — t
- M — a
- S — u
- D — r
- C — a
- A —