Ride sharing backend architecture.
Every driver streams their position every few seconds whether or not anyone is riding, which makes this one of the few consumer systems where writes dominate reads.
The components
Every row below is read from the graph that produced the diagram above, so the two cannot disagree.
| Component | Tier | Why it is there |
|---|---|---|
| Rider app | Client | Supporting component |
| Driver app | Client | Streams location every few seconds |
| API gateway | Edge | Supporting component |
| Location ingest | Application | Highest write volume in the system |
| Matching engine | Application | Geospatial index, not a table scan |
| Trip service | Application | Supporting component |
| Pricing | Application | Surge from live supply and demand |
| Payments | Application | Supporting component |
| Redis geo | Data | Driver positions, short TTL |
| Postgres | Data | Supporting component |
| Kafka | Data | Supporting component |
| Maps & routing | External | Supporting component |
Design decisions worth arguing about
A diagram shows what was chosen. It does not show what it cost, and that is usually the part that matters in a review or an interview.
Location writes get their own path
Driver pings are constant, high volume and individually worthless: only the latest one matters. Routing them through the same service that handles trip state would swamp a store that needs to be durable and transactional with writes that need neither. Separating them lets locations live in an in-memory geospatial index with a short TTL while trips live in Postgres. The cost is two stores holding related data and a window where a driver's position and their trip state disagree.
A geospatial index, not a query over every driver
Finding nearby drivers by scanning and computing distance does not survive contact with a real city. Indexing positions into cells means matching reads a handful of buckets rather than the whole fleet. The tradeoff is at the boundaries: a driver just across a cell edge can be nearer than one inside it, so you have to search neighbouring cells too, and cell size becomes a tuning parameter trading accuracy for work.
Surge is a feedback loop, so it needs damping
Pricing from live supply and demand is the point, but a price that reacts instantly oscillates: it spikes, drivers move in, it collapses, they leave, it spikes again. Smoothing over a window and capping the rate of change makes it stable and makes it lag, which means the price is always slightly wrong. Stable and slightly wrong is much better than accurate and oscillating, both for drivers and for regulators.
The maps provider is a dependency you cannot replace quickly
Routing and ETAs come from outside, and both matching quality and the rider experience depend on them. That means an external rate limit is one of your capacity limits and an external outage is a partial outage for you. Caching routes between common points and degrading to straight-line distance when the provider is unavailable are unglamorous and are what keep the product usable during someone else's incident.
How it changes with scale
Location ingest grows with active drivers and is the dominant cost long before trip volume matters. Matching is naturally partitioned by geography, which is convenient: cities are independent, so scaling is mostly a matter of adding regions rather than making any one of them larger. Peak is intensely local, since demand concentrates around specific places and times.
Where it breaks first
Supply collapse in one area. When drivers are scarce, matching searches wider and wider, each search costs more, and the requests that do match produce long pickup times that cause cancellations, which put those riders back into the queue. The load rises exactly when the system is least able to satisfy it, so a search radius cap and an honest no-drivers-available response are what stop it spiralling.
Draw this yourself
Open the Diagram tab and describe the system. The agent emits a semantic graph rather than coordinates, so you can edit the components and the layout re-solves instead of drifting.
When the shape is right, Implement in code turns the canvas into a markdown specification, every component, every relationship and the notes, and starts a real turn in the Code tab with it.
Questions about this design
More templates
- URL Shortener System DesignThe canonical read-heavy system: roughly a hundred reads for every write, and a redirect that has to be fast enough that nobody notices it happened.
- Chat Application System DesignLong-lived connections change everything: the hard part is not storing messages, it is knowing which of your servers is holding the socket you need to write to.
- Payment System DesignThe only system on this list where being approximately right is indistinguishable from being wrong, and where the provider, not you, holds the truth.
- RAG Pipeline ArchitectureAlmost every RAG system that disappoints is failing at retrieval, not generation, and the architecture is what decides whether you can tell.