Design Uber — System Design Interview Guide
Design Uber is a system-design interview that asks you to build real-time ride-hailing: riders and drivers continuously stream GPS, the system matches them within seconds, and pricing surges with demand. The hard part is geospatial nearest-neighbor search at millions of moving points.
By Alex Chen, Founder, InterviewChamp.AI · Last verified
Reported in interviews at
- Uber
- Lyft
- DoorDash
- Meta
- Amazon
Sourced from Glassdoor, Levels.fyi, and Blind interview reports.
Functional requirements
- Rider requests a ride from current location to a destination
- System matches the nearest available driver within a configurable radius
- Both rider and driver stream live GPS updates every few seconds
- ETA and dynamic price (surge) calculated at request time
- Trip lifecycle: requested, accepted, in-progress, completed, rated
Non-functional requirements
- Match latency: <2 seconds from rider tap to driver assignment (p99)
- Location-update ingest: ~1M drivers × 1 update / 4 sec = 250K writes/sec global peak
- Availability: 99.99%; degraded matching (longer ETAs) acceptable, full outage is not
- Consistency: rider must see exactly one driver assigned; no double-booking
Capacity estimation
Anchor on public 2023 scale: ~150M monthly active riders, ~6M active drivers, ~25M trips/day globally. Location pings dominate write QPS: 6M drivers × 1 ping every 4 seconds = 1.5M location writes/sec at peak (but each city/region is independent, so per-region peak is far smaller — e.g. NYC peak ~50K drivers).
Match QPS: 25M trips/day / 86,400 × 5 (peak factor) ≈ 1,500 match requests/sec globally. Each match must consider drivers within ~2-5 km of the rider, typically 50-500 candidates. The bottleneck is the spatial index: at 1M+ moving points, a naïve scan is impossible, and updates happen every few seconds.
Storage: trip metadata is ~1 KB × 25M trips/day = 25 GB/day = ~10 TB/year. Driver location is hot, not durable — kept in an in-memory geospatial index. Historical location streams (for fraud detection, replay) are written to an append-only log, ~1 KB per ping × 1.5M/sec × 86,400 = ~130 TB/day raw, typically retained 30 days hot, then sampled to cold storage.
High-level design
Riders and drivers run mobile clients that maintain a persistent connection (WebSocket or polling) to a regional API gateway. The system is sharded by geographic region: each region has its own matching service, location index, and trip store, because rides almost never cross regions. This regional sharding is the single biggest design lever — it converts a global problem into 100 independent city-scale problems.
Location pings flow into a streaming ingest layer (driver_id, lat, lng, timestamp). Pings update an in-memory geospatial index — typically a grid + quadtree or geohash-prefix hash. Geohash buckets the world into nested cells: a 6-character geohash is ~1.2 km × 600 m, small enough that nearest-neighbor in a ~5 km radius needs to scan ~25 cells. The index stores (cell_id → set of (driver_id, last_ping_ts)).
Match flow: rider taps 'request'. The match service queries the geospatial index for the rider's cell + neighbors, filters to drivers idle and online, ranks by ETA (distance + traffic factor), and offers the top candidate. On accept, a trip row is written to a sharded relational store, both clients are notified via their persistent connections, and the driver's index entry is marked 'on trip' so they're excluded from further matches. Surge pricing is computed regionally from rolling demand/supply ratios cached in an in-memory tier and refreshed every minute. Historical location pings stream into a durable log for fraud, ETA-model training, and trip replay.
Deep dive — the hard problem
The deep dive is geospatial nearest-neighbor at write-heavy scale. Naïve solutions fail in clear ways. A relational store with a B-tree on (lat, lng) can't do radius queries cheaply because lat and lng are independent; you'd scan one full dimension. Storing a R-tree or quadtree in a relational store works but the per-update lock cost (each driver updates every 4 sec) kills throughput.
The standard answer is an in-memory geohash grid. Geohash encodes (lat, lng) into a string where shared prefixes correspond to spatial proximity — a 6-char prefix is a ~1.2 km cell. The driver location service stores a hashmap of (cell_id → set of driver_ids). Update is O(1): hash the new (lat, lng) to a cell, move the driver from old to new cell. Nearest-neighbor query is bounded: scan the rider's cell plus the 8 neighbors at the same precision (covering ~3.6 km × ~1.8 km), then sort candidates by exact great-circle distance. The grid is replicated across multiple nodes for HA but partitioned by region so a city's load fits on one node's RAM (~500K drivers × ~100 bytes = ~50 MB — trivially in-memory).
Second hard problem: avoiding double-booking. Two riders requesting at the same moment can both see the same driver as the nearest available. The match service must atomically transition driver state idle → reserved → assigned. The standard pattern: a compare-and-set on the driver record (or a leader-per-region match coordinator) that linearizes assignment within a region. The reserve step uses a short TTL — if the driver doesn't accept within ~15 seconds, the reservation expires and they're returned to the pool, so a flaky driver doesn't permanently block matching.
Third tradeoff: ETA accuracy vs match latency. Computing exact ETA requires routing through the road network (Dijkstra or contraction hierarchies on a road graph) and is slow at scale. Production systems use a cheaper great-circle distance plus a learned 'traffic factor' lookup keyed by (origin cell, destination cell, time-of-day) for the match decision, then compute the exact route only after match. Mention both options and pick one with a reason.
Common mistakes
- Using a generic relational store with a B-tree on lat/lng for live driver locations — radius queries are pathological
- Designing a single global match service instead of regional sharding — assumes global rides exist (they almost never do)
- Forgetting the double-booking race condition — interviewer pushes hard on 'what if two riders tap at the same instant'
- Computing exact route-based ETA for every candidate driver during match — turns a sub-second match into a multi-second one
- Storing live driver location in a durable relational store instead of in-memory — write QPS exceeds what disk-backed stores comfortably handle
Likely follow-up questions
- How would your design handle a New Year's Eve surge where demand 20× normal in 30 minutes?
- How would you implement scheduled rides (book now for 8 AM tomorrow)?
- What changes if you have to support driver-to-driver hand-offs for multi-city trips?
- How would you detect and prevent location spoofing by drivers?
- How would you support pooled rides where one car picks up 2-3 riders going similar directions?
Practice Design Uber live with an AI interviewer
Free, no sign-up required. Get real-time feedback on your design.
Practice these liveFrequently asked questions
- How long is a Design Uber system-design round?
- 45–60 minutes typical. Uber's own loop runs 60 minutes and expects you to cover spatial indexing, regional sharding, and surge pricing. Source: Glassdoor Uber 2023–2024 interview reports.
- Do I need to know geohash specifically for Design Uber?
- Geohash is the most-named answer, but quadtree or S2 cells (Google's spatial library) are accepted equivalents. Naming the property — hierarchical spatial buckets enabling O(1) updates and bounded radius scans — matters more than the exact algorithm.
- What's the most common mistake on Design Uber?
- Designing for global rides. Real rides almost never cross regions, so the system is naturally sharded by city. Candidates who design a global match service waste 10 minutes and then have to rework it under interviewer pressure.
- Should I cover the driver app and rider app separately?
- Briefly — one slide each. The interviewer wants the backend signal: ingest, index, match, trip-state. Spending 10 minutes on UI layers is a red flag at senior+ levels.