How your ride app finds a driver

The claim

In my simulated city, the closest car in a straight line was not the fastest by road in 51% of requests.

Why I built it

Anyone who rides has watched an app say “finding your driver”. I wanted to see what happens behind that screen, and to measure it. The page lets you change how the server matches riders and how busy the city is, then watch the waits move.

Click a waiting rider to see the cells the server searched and each car’s straight-line distance beside its road time. A four-step tour walks through the same ideas.

What I built

A dispatch server and simulated phones on real city roads from OpenStreetMap. The server keeps a spatial index of cars, finds candidates, times them by road and offers the trip. The same TypeScript core runs in a browser worker and as a Node WebSocket server for the load test. AI coding agents (Claude Code) wrote the code under my direction. The city is a simplified simulation with no traffic and copies no company’s system.

Tour step 2: the map zoomed to one rider, with the cells searched shaded, beside a table. Car 348 is 121 m away and needs 61 s by road. Car 456 is 321 m away, needs 29 s and is offered the trip. Tour step 2: the map zoomed to one rider, with the cells searched shaded, beside a table. Car 348 is 121 m away and needs 61 s by road. Car 456 is 321 m away, needs 29 s and is offered the trip.
The closest car in a straight line needs 61 s by road. A car further away needs 29 s.

Decisions

  • Server and phones as separate packages. They talk only through messages. One blob would make “the phone talks to the server” untrue.
  • Fastest by road checked over every idle car. Checking only the candidates would hide what the nearby search misses.
  • Load test on closest by road. Batched runs report solve time apart from the window, which is waiting, not work.
  • A replay for the tour. One seed, one method after the other, compared as numbers. Two cities side by side would not fit a phone.

How I tested it

I froze the simulation settings before any measured run, then used 20 seeds for each comparison. Greedy batches every 5 s added 4.0 s to the average wait at normal demand and saved 1.9 s at busy demand. A square grid found nearby cars about 150 times faster than checking every car at 100,000 cars. One Node process kept up with 1.3 million location updates a second on an Apple M4 Pro, then dropped frames. Each of 5,000 cars reported every 3.9 ms over one connection, so almost no update moved a car into a new cell.

What broke

  • My first demand rates filled the fleet. Normal demand left almost no spare cars, so I withdrew every result and reran them.
  • The assignment code padded every batch to a square. Big batches took seconds. Running over the smaller side cut them to milliseconds.
  • The server’s lag timer hid long stalls. I replaced it with one that counts every delayed tick.

What next

Test other fleet sizes on the server, since only 5,000 cars were tried. Add pooled rides and surge pricing, which I left out.