DoorDash started, like most startups, with a single monolithic Python application built on Django, handling consumer ordering, merchant management, and the logistics engine matching Dashers to deliveries all in one codebase. That worked while the company was small, but DoorDash's core problem — real-time matching across a three-sided marketplace of consumers, merchants, and Dashers — has a fundamentally different performance and scaling profile than serving a merchant's menu page. As order volume grew and the engineering org grew alongside it, the monolith became both a genuine scaling bottleneck and a drag on team velocity, since unrelated teams kept colliding on the same codebase and deploy pipeline.

Extracting the highest-value piece first

Rather than attempting a full rewrite, DoorDash's engineering blog describes a phased migration that started by extracting the components with the most distinct, independently-scalable workload — most notably their logistics and dispatch systems. The assignment algorithm that matches Dashers to orders, considering factors like estimated arrival times and the batching of multiple orders into a single trip, is a real-time optimization problem with little in common, performance-wise, with rendering a storefront page. Giving it its own service boundary meant it could be scaled, deployed, and iterated on independently of the rest of the platform, without dragging unrelated request paths through the same release cycle.

Building the platform before the sprawl

Having watched peers in the industry — Airbnb among them — run into significant pain from uncoordinated microservices sprawl, DoorDash invested deliberately in an internal service platform rather than letting every team build services their own way. That meant standardizing how services were created, deployed, observed, and how they communicated with each other, largely over gRPC internally, so that consistent tooling was a prerequisite for decomposition rather than something bolted on after dozens of inconsistent services already existed.

Observability and compatibility during the transition

Splitting a monolith into services doesn't remove complexity, it relocates it — a single in-process function call becomes a network call that can fail, time out, or arrive out of order. DoorDash has written about the corresponding investment in distributed tracing and observability tooling needed to debug requests that now span many services instead of one call stack, and about the discipline of backward compatibility and contract testing between services so that consumer- and merchant-facing apps kept working correctly throughout the migration rather than breaking in production.

Regional isolation for a regional business

Delivery logistics are inherently local — a Dasher in one city has no bearing on an order in another — so DoorDash, like other high-growth marketplace companies, has discussed geographic or cell-based partitioning of infrastructure, keeping the blast radius of a regional infrastructure issue contained to that region rather than letting it cascade platform-wide.

What you can borrow

  • Extract the piece of your monolith with the most distinct scaling or performance profile first, not necessarily the easiest piece to pull out.
  • Build the internal platform and tooling for creating and operating services before you have dozens of teams inventing their own conventions independently.
  • Invest in distributed tracing at the same time you start splitting services, not after debugging across service boundaries has already become painful.
  • Partition infrastructure along your business's real geographic or logical boundaries to contain blast radius, if your workload has a natural regional shape.