Why a taxi app breaks at peak orders
Peak orders expose what is invisible on a test bench: a taxi app stops being a set of screens and turns into a real-time system where a one-second desync costs a lost driver.
Matching is the first to give way — during an order storm, two dispatch nodes simultaneously assign the same driver to two rides, and both passengers see "car on the way" while the driver sees one address.
Then the cascade: the order goes into a queue, but the assignment event is lost, and the ride hangs in the "search" status — the passenger cancels, the driver idles, and the service pays for both idle times with its reputation.
Geolocation accuracy in a taxi app breaks differently. In dense urban development, the phone reports coordinates with drift, and with a weak signal — with a delay of tens of seconds.
If the driver's point updates less often than their position changes, the map draws the car where it no longer is, and the pickup time estimate lies. The passenger goes outside and can't find the car — formally the system worked, in practice the ride is ruined.
The problem isn't the map itself, but how the app filters and smooths the stream of coordinates before sending them to the server.
A separate class is payment failures. The linked card is charged at the moment the ride ends, and if the acquiring service responds slowly or returns an ambiguous status, the money isn't charged, yet the ride is closed.
A repeated request without idempotency leads to a double charge — and this is no longer a technical glitch, but refunds, complaints, and lost trust.
Driver matching failures, stuck taxi orders, and payments are one connected chain, where the weakest link determines the reliability of the entire service.
The cost of each bottleneck is measured not in lines of code, but in ruined rides: the share of orders that reach completion, the time from request to driver assignment, the share of cancellations caused by the system.
Let's break down how this is closed at the architecture level — from queues and locks to coordinate processing and idempotent payments.
Taxi Service Architecture: Matching, Geo, Payments
Developing a taxi app rests on three subsystems, each measured by its own metrics. Matching determines how many seconds it takes for an order to find a driver and what share of rides can be completed without cancellations.
Geolocation in a taxi app sets the accuracy of the position and the smoothness of the track on the map. Payment integration in a taxi — the share of successful charges and the speed of refunds upon cancellation. Below is how these parts are structured and what each one delivers.
Matching: Greedy vs. Batch
The greedy driver matching algorithm assigns an order to the nearest available driver immediately — simple, but in peak zones it causes skew: some cars drive across the entire district, and pickup time grows.
Batch matching collects requests in windows of a few seconds and solves the distribution problem as a whole — pickup time drops, the share of successful orders grows, but a delay is introduced for the window itself. In practice, a hybrid is used: greedy in the center, batch in dense zones.
Geolocation: Frequency and Accuracy
A driver's position arrives as a stream; if every point is written to the database, it chokes on hundreds of cars. Points are buffered in memory and flushed in batches, while the map receives a smoothed track — without jerks or "teleports" between measurements. This reduces the load on storage and eliminates false geofence triggers.
Payments: Idempotency and Refunds
A charge is tied to a ride by a unique key, so a repeated request does not create a second charge. A cancellation initiates a refund with the same key — money doesn't get "stuck," and disputed transactions are resolved via the log.
| Option | When it fits | Limitations |
|---|---|---|
| Greedy matching | Low order density, small city | Skew at peaks, pickup time grows |
| Batch matching | Dense zones, high competition for orders | Window delay, harder to debug |
| Hybrid | Cities with mixed load | Thresholds need tuning by zone |
| External payment gateway | Fast start, ready-made payment methods | Dependence on someone else's SLA |
| Own payment layer | Full control over refunds and logs | More work at the start |
PostGIS for geo queries and building zones — a proven layer that handles spatial queries under load.
Each subsystem covers its own class of failures: matching — long pickups and cancellations, geo — inaccurate tracks and false geofences, payments — double charges and stuck refunds.
How we run a project: from audit to handover
Developing a taxi app rarely starts from a clean slate: more often there is a dispatch system, a driver app, an order database, and a set of integrations that already work — and break under peak-hour load.
That is why we run a project as an engineering sequence, where each stage produces a verifiable artifact rather than a status in a report. Below is how it works.
- Audit of the current state. We examine existing services, data schemas, matching logs, and support complaints. The output is a map of bottlenecks: where orders get lost, at which step the driver and passenger see a desync, and which integrations are holding the system together.
- Service diagram and boundaries of responsibility. We fix what stays, what gets rewritten, and what gets moved to a separate contour. We define contracts between matching, geolocation, payments, and notifications — so that changes in one do not bring down the others.
- Matching and geolocation. We configure search radii, driver priorities, handling of "cold" zones and order queues. Separately — behavior when the signal is lost: an order must not hang in the "looking for a car" status.
- Payments and trip statuses. We run the end-to-end payment path: holding, charging, refund, cancellation at various stages. We verify that the order state in the app, on the server, and in the payment contour match.
- Load testing. We run peak-hour scenarios: a surge of orders, a mass influx of drivers going on line, loss of connectivity for some clients. We look at matching response time and the share of orders brought to driver assignment.
- Handover of the project to the customer. We hand over repositories, service diagrams, environment configurations, deployment instructions, and the release policy. The customer's team can build, roll out, and roll back a version without us.
Each stage ends with a demonstration on real data, not a presentation. If the audit shows that fixing the existing contour is cheaper than building a new one, we say so directly. Describe your task: we will come back with an estimate and a stage-by-stage plan.
How does custom matching differ from an off-the-shelf one?
An off-the-shelf solution for taxi is a matching library inside the platform: you feed it the coordinates of drivers and orders, and get back a "driver–order" pair. The logic is closed: nearest available, queue, simple priorities.
For a city with steady demand and a fleet of a few dozen cars, this is enough — faster launch, nothing to maintain, predictable behavior.
Custom order matching begins where the off-the-shelf logic stops accounting for your economics.
Taxi order distribution is not "who is closer," but a constrained problem: keep a driver in a high-demand zone, prevent them from idling after a long pickup, balance load between districts, factor in rating, car class, and cancellation history. An off-the-shelf solution either doesn't support such weights or hides them behind fixed settings.
The difference shows up in metrics, not in the description. A custom algorithm lets you change priorities without an app release — the rule lives on the service side, and the driver gets the updated logic immediately.
This yields a controllable percentage of pickups within the target time, reduced deadhead mileage, and predictable load during peak hours. The flip side is that matching becomes your responsibility: tests, load, version rollback, anomaly monitoring.
Comparing matching approaches comes down to three questions. How much does your economics differ from the typical one — if it differs a lot, the off-the-shelf logic will drag you down.
Do you have the data and the people to tune the weights — a custom algorithm is useless without an analyst.
And how fast do you need to launch — an off-the-shelf solution gets the product to market sooner, while custom matching pays off at volume, when every fraction of a percent of pickups converts into fleet revenue.
A practical path is a hybrid: start on off-the-shelf logic, collect telemetry on pickups and cancellations, then move matching into a separate service once enough statistics for weights has accumulated.
That way you don't pay for complexity ahead of time and don't hit the ceiling of the off-the-shelf solution when the fleet grows.
Case study: how the share of successful orders grew
The service ran on contractor-built assembly: a request went to a manual dispatcher, the driver received the address as text and called to clarify the entrance.
At the peak of evening hours this produced up to 28% cancellations — the passenger left for a competitor while the car circled the courtyard.
We captured the taxi service metrics from two sides: events in the passenger app and in the driver app, plus the dispatcher log — the discrepancy between them showed where orders were being lost.
What was changed in matching and geolocation
We rewrote the distribution core: a request no longer waits for a free dispatcher, but goes directly to the nearest drivers, taking into account their current heading and street congestion.
We determine the pickup point by the building's geocode, not by the district centroid — the driver arrives at a specific entrance, and the "where are you standing" call disappears from the scenario. This removed the main cause of cancellations at the pickup stage.
What was changed in payment
The payment scenario was moved to card tokenization on the first order: charging happens without re-entering data and without a redirect to a third-party page.
The share of payments failing at confirmation dropped from 9% to 1.5% — the driver sees the payment status before the trip starts and does not cancel the order due to the risk of not getting paid.
| Metric | Before | After |
|---|---|---|
| Average pickup time | 11 min | 6 min |
| Share of order cancellations | 28% | 9% |
| Pickup geoposition accuracy | ±180 m | ±25 m |
| Successful payments on first attempt | 91% | 98.5% |
Data was collected for two weeks before the release and three weeks after, on a comparable volume of orders and during the same hours — the comparison is valid, not "before the holidays and after." Separately, we calculated the share of successful taxi orders out of created ones: growth from 64% to 88%, meaning every fourth lost order returned to revenue without attracting new drivers.
The matching is built on an open stack of geospatial queries — PostGIS maintains an index on coordinates and returns the nearest drivers in fractions of a second even on a dense stream.
What if drivers work on old phones? The matching core lives on the server, the driver app only sends coordinates and receives an order — the requirements for the device have not increased, and the update proceeds without replacing the fleet.
How did you roll back if the metrics dropped? The release went behind a toggle: the old distribution logic remained operational, and the rollback took minutes and did not require pushing a new build to the app stores.
The client receives the order flow diagram, the configuration of geo-indexes, the payment retry policy, a dashboard for the service metrics, and a support guide. Describe your task — we will come back with an estimate and a plan.
Doubts before the start: access, migration, rollback
Before starting development of a taxi app, the client usually has three questions in mind: what will have to be handed over to the contractor, how the migration of the taxi service from the current solution will go, and what will happen if a new release breaks rides during peak hours.
We answer directly: access to infrastructure is granted on a least-privilege basis — separate accounts with a limited role, keys to the payment loop and cards remain with you, and our engineers work through your access control with an action log.
What if a rollback of changes is needed? Every release is rolled out in stages: first to a share of drivers and passengers, then to the entire flow.
Until the new version has proven stable, the previous build remains ready for return — switching takes minutes, not hours of downtime.
The data schema during migration is expanded compatibly: old and new fields coexist, so a rollback does not require reverse transformation of the database.
What if you already have a working service? We migrate in parts — first matching and geolocation, then payments and ride history, verifying metrics at every step. Until the new loop shows comparable results, the old one continues serving orders.
Who owns the source code and access after handover?
Everything remains with you: repositories, infrastructure accounts, keys, domains. We hand them over by act and revoke our own access — further support proceeds under a separate agreement.
The client receives:
- environment configurations (development, test, production) with a description of variables;
- architecture and data flow diagrams: geolocation, matching, payments, notifications;
- access policies and a role model for the team and contractors;
- release regulations and the procedure for rolling back changes with criteria;
- project documentation: description of modules, integrations, and failure points;
- instructions for monitoring key metrics — pickup time, cancellation rate, payment errors.
Describe the task and the current loop — we will come back with an estimate, a migration plan, and the composition of the documentation to be handed over.







