Design Uber: the ride-sharing system design interview walkthrough
The request to design Uber sounds like one line — match a rider to a nearby driver — and that one line hides the whole interview. Doing it for one city block is a lookup anyone can write; doing it for ride-sharing system design at real scale, with millions of drivers whose locations change every few seconds and a closest-driver answer that has to come back in milliseconds, is a geospatial and high-write-throughput problem with a dozen sharp edges. This is one of the most common system design interview prompts because it forces every real-time muscle at once: ingesting a firehose of location-based service updates, indexing them so proximity queries are fast, and matching exactly one driver to one rider without a double-booking.
The round turns on two decisions. First, where the driver pings go: a million drivers pinging every few seconds is a write volume no disk database survives on the hot path, so the location data lives in an in-memory, partitioned store — and you cannot query raw latitude and longitude for "who is near me" efficiently anyway. Second, how you make that store answer proximity fast: you divide the map into cells with a geospatial index — geohash, QuadTree, or Google S2 — bucket drivers by cell, and look up the rider's cell plus its neighbours. This walkthrough follows the path a strong candidate draws on the whiteboard — requirements, estimate, API and data model, high-level design, then the deep dives — and at each step it names the follow-up the interviewer uses to tell a memorised answer from an engineer who understands the tradeoff.
How the ride-sharing round is actually run
A ride-sharing round runs about 45 minutes on a shared canvas — you draw the boxes and arrows while you talk, and the interviewer interrupts. It is a favourite because the one-line problem opens onto deep water fast: the moment you draw "find a nearby driver," the question becomes how fast, at what write load, and against which index, and every answer after that (ingestion, geo index, matching, trip state) compounds. Get the geospatial spine clean and you have shown the whole loop in one problem.
The interviewer rarely lets "look up nearby drivers" stand. "A million drivers ping their location every four seconds — where do those writes go, and how do you still answer 'who is near this rider' in a few milliseconds?" That question — high-write ingestion plus fast proximity search — is the one a copied answer can never rehearse, and the one this round is really testing.
The rounds here are modelled on how the loop runs, not scraped from any candidate's recall. What matters for prep is the shape: a blank canvas, a clock, and a follow-up after every decision you make.
The five-step framework for any HLD round
Every high-level design answer fits the same spine. Say it out loud at the start so the interviewer knows you have a plan, then walk it:
- Clarify requirements — functional and non-functional, and what you are explicitly not building (pricing, surge, payments can be scoped out).
- Estimate — active drivers, ping frequency and ride requests, to justify every later choice.
- High-level design — the boxes and arrows: rider and driver apps, gateway, location service, matching service, trip service.
- Deep dive — the two or three decisions that actually decide this system.
- Follow-ups — bottlenecks, failure modes, and what you would change at ten times the scale.
What the debrief measures
The score is not "right or wrong design." A good HLD debrief reads on four dimensions, and every move below maps to one: problem solving (did you reach an in-memory geo index that ingests pings and answers proximity fast), communication (did you say why you cannot query raw lat/long in a sentence), technical execution (cell bucketing, neighbour-cell search, one-driver-one-rider locking, a consistent trip state machine), and time management (did you spend your 45 minutes on ingestion, indexing and matching, not on the pricing model). Reading a walkthrough teaches the design; it does not teach you to deliver it on the clock while someone pushes back.
Step 1 — Requirements: what a ride-sharing platform must do
Spend the first few minutes here. Jumping to boxes before you have scoped the problem is the fastest way to design the wrong system.
| Type | Requirement |
|---|---|
| Functional | A rider requests a ride; the system finds nearby available drivers and matches and dispatches one; it tracks the driver's and the trip's location in real time; it drives the trip through its lifecycle — requested → matched → en route → ongoing → completed. |
| Functional (optional) | Pricing and surge, ETA estimation, in-app payments — call these out, then scope most of them for later. |
| Non-functional | Low-latency matching (closest-driver answer in milliseconds); very high write throughput for location pings; highly available; the state of any one trip stays consistent (a driver is matched to exactly one rider). |
The sentence that earns points: "The core of the design is ingesting a flood of location pings and answering proximity queries fast — that is a geospatial indexing problem, not a storage problem; everything else hangs off it." Say that and you have framed the entire design before drawing a box.
Step 2 — Capacity estimation (back of the envelope)
Estimation here is about proving one thing: the hard axis is location write volume plus proximity query latency, not how many bytes you keep.
| Quantity | Assumption | Result |
|---|---|---|
| Active drivers | ~3M online at peak | each is a moving point to track |
| Location pings | every ~4 sec per driver | ~750K location writes/sec |
| Ride requests | ~1% of drivers' volume at peak | a proximity query on every request |
| Location row size | tiny (id, lat, lng, ts) | storage is cheap; the write rate is not |
Two conclusions fall out, and you should say both out loud. First, the pings are the system — three-quarters of a million writes a second means the design is dominated by how you ingest and index locations, not by how you store history. Second, every ride request fires a proximity query ("drivers near this point"), and a plain B-tree index cannot answer that — it sorts one dimension at a time while nearness is two-dimensional. You have now pointed the interview at the real deep dives — location ingestion and the geospatial index — before anyone asked.
Step 3 — API and data model
The API splits in two: high-frequency location writes from drivers, and the ride lifecycle calls from riders. Live updates ride a push channel; everything else is ordinary HTTP.
| Channel | Operations |
|---|---|
| Location (high write) |
updateLocation(driverId, lat, lng, ts) → location service, fired every few seconds per driver. |
| Ride lifecycle (HTTP) |
requestRide(riderId, pickup, dropoff); acceptRide(driverId, tripId); getTrip(tripId); live trip updates pushed over WebSocket / push to both apps. |
The data model is thin. The hot location data is ephemeral and lives in the geo store; the durable record is the trip:
| Entity | Notes |
|---|---|
driver_location |
driver_id, lat, lng, cell_id, updated_at, status (available / on-trip) — kept in the in-memory geo index, not on the hot disk path. |
trip |
trip_id, rider_id, driver_id, state, pickup, dropoff, created_at — the durable source of truth, server-stamped state transitions. |
driver / rider
|
profile, rating, vehicle — ordinary relational rows, read off the hot path. |
Step 4 — High-level design: the location-service architecture
Now the boxes. The rider app and driver app talk to an API gateway; behind it sit three services that carry the round. The one structural insight to name: driver locations do not go to a disk database on the hot path — they go to an in-memory, partitioned location service that doubles as the geospatial index.
- Rider app and driver app: the driver app streams location pings; both receive live trip updates over a WebSocket / push channel.
- API gateway: the single entry point — auth, rate limiting, routing to the right service.
- Location service (in-memory geo index): absorbs the ping firehose and answers "drivers near this point" in milliseconds; partitioned by map cell so writes and queries spread across machines.
- Matching service: on a ride request, asks the location service for nearby candidates, ranks and offers them, and makes the chosen driver exclusive to that trip.
- Trip service + trip DB: creates the trip and drives it through its state machine with consistency; the durable store.
Say explicitly that raw latitude and longitude are not queryable for proximity — you need a geospatial index that buckets drivers into cells. That single observation is what sets up the two indexing deep dives, and naming it unprompted is a signal the interviewer is listening for.
Deep dive: location ingestion at scale
This is the first decision that actually decides the round. Where do three-quarters of a million location writes a second go?
Not to a SQL database. A driver pings every few seconds; a relational table with indexes would spend all its time rewriting index pages, and the write amplification alone sinks it. The location service keeps the current position of every driver in an in-memory, partitioned store (think a sharded key-value / geo store such as Redis Geo), where each write is an overwrite of that driver's latest point — you never need ping history on the hot path, only "where is this driver now."
| Approach | How it works | The catch |
|---|---|---|
| Disk SQL table, lat/lng columns | Insert or update a row per ping; index lat and lng. |
Dies on write volume, and a two-column B-tree still cannot answer "near this point" without a scan. |
| In-memory store, raw lat/lng | Keep the latest point per driver in RAM. | Survives the writes, but a proximity query is still a full scan of every driver. |
| In-memory store + geospatial index | Overwrite latest point and bucket the driver into a map cell. | The right fit — absorbs the writes and makes "who is near me" a cell lookup, not a scan. |
Pick the in-memory, partitioned geo store and say why in one line: locations are high-churn and ephemeral, so you overwrite the latest point in RAM and partition by region, keeping the disk database for the durable trip record only. The partitioning matters — spread the ping load across machines by map region so no single node takes the whole firehose.
"A million drivers ping every four seconds — where do those writes go?" An in-memory, partitioned geo store, overwriting each driver's latest point, not a SQL table on the hot path. Only the trip lives in the durable DB. Naming "overwrite, don't append" and "partition by region" is the senior move.
System Design
HLD · Payments & marketplaces · 45 min
Design Uber out loud, with follow-ups
Deep dive: geospatial indexing and nearby-driver search
This is the decision the whole round is built around. You have the pings in memory — now make "who is near this rider" a cheap lookup instead of a scan over millions of drivers.
The idea is the same across every real system: divide the map into cells, bucket each driver into the cell their coordinates fall in, and answer a proximity query by looking up the rider's cell plus its immediate neighbours and returning the drivers in them. You never compare the rider against every driver — only against the handful in nearby cells. The three ways to cut the map are what the interviewer wants you to compare.
| Index | How it cuts the map | Tradeoff |
|---|---|---|
| Geohash | Encodes lat/lng into a short string; shared prefix = same region, so nearby points share a prefix and a radius query becomes a prefix/range scan. | Simple, fixed grid; cell size is uniform regardless of density, and points near a cell edge need neighbour-cell queries. |
| QuadTree | Recursively splits a region into four quadrants, subdividing only where there are many points. | Adapts to density — a dense downtown splits deep, an empty highway stays coarse — but the tree must be maintained as drivers move. |
| Google S2 / H3 | Maps the sphere to cells on a space-filling curve (S2) or hexagons (H3); hierarchical cell ids. | Uniform cells, clean neighbour math, handles the globe without edge distortion; more machinery to explain. |
The comparison the interviewer is listening for: geohash is a fixed grid, QuadTree is adaptive to density. A fixed grid is trivial to shard (partition by prefix) but wastes cells where drivers are sparse and overflows cells where they are dense; a QuadTree keeps each cell's count bounded by subdividing hot areas, at the cost of maintaining the tree as points move. For an interview, geohash-with-neighbour-lookup is the cleanest answer; name QuadTree and S2 to show you know the fixed-vs-adaptive tradeoff. Always query the rider's cell plus its neighbours, because the closest driver can sit just across a cell boundary.
"A rider is at the edge of their cell and the nearest driver is in the next cell over — does your index miss them?" No — a proximity query always reads the target cell and its eight neighbours, so a driver just across the boundary is still a candidate. That edge case is the one a memorised "use geohash" answer forgets.
System Design
HLD · Geospatial index · 45 min
Defend geohash vs QuadTree out loud
Deep dive: matching, dispatch, and the trip state machine
The geo index hands you candidate drivers; matching turns candidates into one committed trip, and the trip then has to move through its states without ever losing consistency.
The flow is a short pipeline. A ride request arrives → the matching service asks the location service for nearby available drivers → it ranks them (distance, ETA, rating) and offers the ride to the best one → the driver accepts → a trip is created and both parties are notified → the trip walks its state machine. The states are the lifecycle from Step 1: requested → matched → en route → ongoing → completed (with cancelled as an exit at several points), and each transition is server-stamped on the durable trip record so both apps render the same state regardless of their own clocks.
The consistency trap is the one to name unprompted: one driver must be matched to exactly one rider. If two riders request at the same instant and the only nearby driver is one car, a naive design offers that driver to both and double-dispatches. The fix is to make the driver exclusive at match time — lock or conditionally update the driver's status to on-trip (a compare-and-set / conditional write), so the second request's attempt fails and it falls back to the next candidate. The driver is offered to one rider at a time, with a short accept timeout; if they decline or time out, the offer moves to the next candidate.
- Ride request → matching service.
- Query the geo index for nearby available drivers (target cell + neighbours).
- Rank candidates (distance / ETA / rating); offer to the best one with a timeout.
- On accept, atomically flip that driver to
on-trip— if the flip fails (already taken), offer the next candidate. - Create the
trip, notify both apps, and drive the state machine to completion.
"Two riders request at the same instant and the only nearby driver is one car — what happens?" The driver is made exclusive at match time with a conditional status flip; exactly one request wins the compare-and-set, the other falls back to the next candidate. Never offer the same driver to two trips at once.
The follow-ups that decide the ride-sharing round
Scan the probes from each deep dive and they rhyme. Master these five and you are ready for a version of the question that is not drawn exactly like this one.
- "Where do a million location writes every four seconds go?" → An in-memory, partitioned geo store, overwriting each driver's latest point; only the trip is durable. (problem solving)
- "Why can't you just index lat and lng and query a box?" → A B-tree sorts one dimension; proximity is two-dimensional, so you need a cell-based geospatial index, not column indexes. (communication)
- "Geohash or QuadTree — which and why?" → Geohash is a simple fixed grid that shards by prefix; QuadTree adapts to density by subdividing hot cells; name the fixed-vs-adaptive tradeoff. (technical execution)
- "The nearest driver is just across a cell boundary — do you miss them?" → No — always query the target cell plus its neighbours. (technical execution)
- "Two riders, one nearby driver, same instant — what happens?" → Make the driver exclusive with a conditional status flip; one request wins, the other falls back. (problem solving)
Each maps to a debrief dimension. Reading this list teaches you the answers; it does not teach you to deliver them while someone interrupts — that only comes from saying them out loud.
How to prepare: a one-week plan
You do not need fifty systems. You need this one cold and the framework automatic, so the next "design X" is just new nouns in the same five steps.
- Days 1–2: Learn the five-step framework until it is reflex. Do the ride-sharing design end to end on paper — requirements, estimate, API, data model, the location-service boxes.
- Days 3–4: Drill the geospatial deep dives: why raw lat/long is not queryable, the in-memory partitioned store, and geohash vs QuadTree vs S2. Say each tradeoff in one sentence.
- Days 5–6: Own matching and trip state — rank-and-offer, the one-driver-one-rider conditional flip, and the requested → completed state machine. Answer each of the five follow-ups out loud.
- Day 7: A full timed 45-minute round, narrating the whole way, with someone (or something) interrupting. This is the rep that counts.
This post is the deep walkthrough behind one row of the broader Top 25 system design interview questions — once ride-sharing is automatic, the same spine carries the rest of the list.
Frequently asked questions
How do I design Uber step by step?
Follow the five-step HLD framework. Clarify requirements (request a ride, find and match a nearby driver, track location in real time, drive the trip through its lifecycle); estimate to show the hard axis is location write volume and proximity-query latency, not storage; define the high-frequency location writes plus the ride-lifecycle API, with a thin driver-location and trip model; draw the location-service architecture (rider and driver apps, gateway, in-memory geo index, matching service, trip service); then deep-dive the decisions that matter — location ingestion at scale, the geospatial index and nearby-driver search, and matching with the trip state machine. Finish on the follow-ups about cell boundaries and double-dispatch.
Why can't you query raw latitude and longitude for nearby drivers?
Because proximity is two-dimensional and a B-tree index is one-dimensional. An index on latitude narrows one axis and an index on longitude narrows the other, but "within this radius of a point" needs both at once, so the database still scans every driver in a bounding box. A geospatial index fixes this by cutting the map into cells and bucketing each driver into a cell, so a nearby-driver query reads only the rider's cell and its neighbours instead of scanning millions of rows.
What is a geospatial index, and how do geohash, QuadTree, and S2 differ?
A geospatial index divides the map into cells and groups points by cell so proximity queries are cheap. Geohash encodes coordinates into a string where nearby points share a prefix, giving a simple fixed grid you can shard by prefix. A QuadTree recursively splits a region into four quadrants, subdividing only where points are dense, so it adapts to density at the cost of maintaining the tree. Google S2 (and Uber's H3) map the sphere onto a space-filling curve or hexagons for uniform cells and clean neighbour math. The headline tradeoff is fixed grid versus adaptive to density.
How does Uber handle millions of location updates per second?
It keeps driver locations off the hot disk path. Each driver pings every few seconds, which is hundreds of thousands to millions of writes per second, so the location service holds the current position in an in-memory, partitioned store and overwrites each driver's latest point rather than appending history. The store is sharded by map region so the write load spreads across machines, and it doubles as the geospatial index. Only the durable trip record goes to a disk database.
How does the system avoid matching one driver to two riders?
By making the driver exclusive at match time. When a driver accepts an offer, the matching service does an atomic conditional update — a compare-and-set that flips the driver's status to on-trip only if they are still available. If two requests race for the same driver, exactly one compare-and-set succeeds and the other fails and falls back to the next candidate. Combined with a short accept timeout that releases a non-responding driver, this keeps every driver matched to exactly one rider.
How do I practise a ride-sharing system design interview out loud?
Reading a walkthrough is not the same as defending a design while someone interrupts. On Zynter, the HLD track runs live voice rounds modelled on how real system design loops run — you talk through a shared design on the canvas, and a voice interviewer pushes with the follow-ups above one at a time, then gives you a debrief on problem solving, communication, technical execution, and time management. The score is a practice signal, not a hiring decision.
Practise it on Zynter
You can read every cell lookup. The round is won on the ingestion load and the double-dispatch case.
Pick the ride-sharing round, draw the location-service design on the canvas, and defend it out loud to a voice interviewer that keeps asking "where do the writes go?" and "two riders, one driver — what happens?" — then read the debrief. Zynter.ai is on-demand mock interviews modelled on real company loops, with 150+ rounds across 20 companies including the HLD system design track (Sep 2026).
Practise the ride-sharing round →
Browse all interview tracks




Top comments (0)