How Yandex Taxi Extracted Its Pricing Algorithm from Legacy Code (And Why It Wasn't Obvious at the Start)

The engineering team behind Yandex Taxi published an in-depth case study on Habr that chronicles a challenging refactoring project: extracting the pricing algorithm from the core codebase of the taxi platform. The story is an illuminating example of how architectural debt accumulates silently, and why some of the most critical business logic can become almost impossible to modify safely.

For engineers who have ever fought with a legacy system, this case study resonates because it exposes the gap between the mental model of an algorithm and its real-world implementation. At its core, a pricing algorithm in a ride-hailing app appears to be a simple function — take distance, duration, and traffic, apply a tariff, and return a price. But in a mature platform like Yandex Taxi, pricing interacts with surge detection, driver availability, promotions, corporate billing, and dozens of microservices. The article reveals that the algorithm had grown organically over years, with business rules embedded in multiple places and coupled to internal state that was poorly isolated.

The Hidden Cost of Embedded Logic

The developers describe how what initially looked like a self-contained module had actually become a 'god object' — a piece of code that too many other parts of the system depend on. They encountered concrete problems:

  • Excessive coupling: Pricing calculations could not be reused by other teams without pulling in the entire taxi core.
  • Fragile tests: Unit tests required mocks for dozens of dependencies, and meaningful test coverage was nearly impossible.
  • Deployment risk: Even a small change to pricing logic triggered a full regression test of the entire booking pipeline.
  • Poor observability: It was hard to answer basic questions like 'what tariff was applied to this ride?' because logs were scattered.

The material points out a subtle but important insight: the pricing algorithm was never deliberately designed as a shared library. It grew through incremental additions, with each change appearing safe in isolation. This phenomenon is familiar to developers in industries like finance, e-commerce, and logistics, where pricing or rule engines are often hardcoded into the heart of the system.

Why Extraction Wasn't an Obvious Priority

The article also examines the organizational reasons why this debt went unnoticed for a long time. The team's initial mental model was that pricing was a simple formula. Code reviews focused on the specific change rather than the overall dependency graph. And because the business was shipping new features rapidly, an inline modification was often faster than building a proper abstraction.

The turning point came when the team started treating the algorithm as a system rather than a function. They used static analysis to map all callers and data flows. The results were surprising: the algorithm had interfaces with dozens of other modules, including some that were not obvious at first. For example, the discount service and the surge detection module both read and wrote the same pricing state, creating a subtle dependency that only became visible when the team tried to isolate the algorithm.

The Extraction Roadmap

Based on the experience, the article outlines a practical migration strategy:

  1. Define a stable pricing API: Create a clean interface that captures the inputs and outputs of the calculation, independent of the rest of the system.
  2. Add a feature flag: Implement a toggle that routes pricing requests either to the legacy inline code or to the new external module.
  3. Migrate gradually: Move a small percentage of real traffic to the new module and compare the results with the old implementation.
  4. Build a golden-master test suite: Collect thousands of historical ride records and use them as regression tests to verify the new algorithm produces identical outputs.
  5. Eliminate hidden state: The old algorithm relied on mutable globals, such as the current time, user session, and live traffic cache. The team had to systematically remove these dependencies before the new module could be deterministic.
  6. Use contract testing: Instead of massive integration tests, they created consumer-driven contract tests for each caller of the pricing API.

Before and After Comparison

Aspect Before (embedded) After (extracted module)
Deployment Any change required deploying the entire monolith Independent deployment of the pricing service
Scalability Pricing scaled with the whole application, leading to waste Separate autoscaling for price computation
Testing Integration tests with heavy mocks and low confidence Targeted unit tests and contract tests
Change Impact A small change could have side effects across booking and payment Changes are isolated behind a versioned API
Observability Logs scattered across components Centralized tracing, metrics, and dashboards

Key Takeaways for Engineering Teams

The case study offers several lessons that go beyond ride-hailing:

  • Beware of implicit architecture: If your codebase develops a 'web of dependencies' around a supposedly simple function, it's a sign that the function has become a platform.
  • Invest in static analysis tools: Mapping dependencies proactively helps identify extraction candidates before they become critical.
  • Plan for a long migration: The authors stress that extraction is not a sprint. It requires cross-team coordination, a clear rollback strategy, and a willingness to postpone new features.
  • Version your algorithms: Treat pricing (and similar algorithms) as a product with its own API, versioning, and lifecycle.

Conclusion

The Yandex Taxi team's story is a valuable reminder that technical debt often hides in the most business-critical parts of a system. The extraction they performed was not a simple refactor — it was an architectural rehabilitation that changed how the entire company thinks about pricing. For any engineering organization wrestling with legacy code, the article is a rare and honest look at the problem — and a practical guide to solving it.

Source

← All posts

Comments