System design template

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.

Download for macOS
v0.1.33 · .dmg · Apple Silicon & Intel
Signed & notarized by Apple · opens without a Gatekeeper warning
sha256 698955a0187bc039f4c74f5d05a9f10fbb27376a45788a0a241d1326b73873c7
Download for Windows instead
$curl -fsSL https://lucidtrain.com/install.sh | sh
Ride sharing backend architecture. 12 components across 5 tiers.
Ride sharing backend architecture. Rendered by the same ELK layout engine the app runs: the agent emits components, tiers and edges, and the engine places them, so the boxes cannot overlap.

The components

Every row below is read from the graph that produced the diagram above, so the two cannot disagree.

ComponentTierWhy it is there
Rider appClientSupporting component
Driver appClientStreams location every few seconds
API gatewayEdgeSupporting component
Location ingestApplicationHighest write volume in the system
Matching engineApplicationGeospatial index, not a table scan
Trip serviceApplicationSupporting component
PricingApplicationSurge from live supply and demand
PaymentsApplicationSupporting component
Redis geoDataDriver positions, short TTL
PostgresDataSupporting component
KafkaDataSupporting component
Maps & routingExternalSupporting 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.

shell
$ Diagram a ride sharing backend: rider and driver apps, location ingest, a geospatial matching engine over Redis, trip service, surge pricing from live supply and demand, payments and an external maps provider.

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.

FAQ

Questions about this design

Why Redis rather than PostGIS for driver locations?

Because the data is ephemeral and the access pattern is a radius query against constantly changing points. An in-memory index with TTL fits that exactly. PostGIS is the better choice for geography that persists and needs to be joined against other tables.

How often should drivers report their location?

Often enough that matching is accurate, rarely enough that phones survive a shift. Every few seconds while carrying a passenger and less frequently when idle is the usual compromise, since an idle driver's exact position matters much less.

How do you prevent two riders matching the same driver?

A short lock on the driver during the offer window, released on decline or timeout. Without it, two matching workers can offer the same driver simultaneously and one rider gets a confirmation that is then revoked.

What happens when a driver goes offline mid-trip?

The trip continues, since trip state does not depend on location. The rider loses live tracking, which is a degraded experience rather than a failure, and the trip completes from the driver's app when connectivity returns.

More templates