5. Ride-Sharing (Uber) Advanced + Geospatial

A. Realistic Interview Prompt

"Design a ride-sharing system like Uber: riders request a trip, nearby available drivers are found and matched to the request, and both parties can track the trip in real time."

This problem introduces a dimension none of the previous four have: geography. Every prior design point (fanout, routing, ordering) assumed identity-based lookups (a user ID, a client ID); here the fundamental query is "who/what is physically near this point," which a standard database index cannot answer efficiently at all. The second, equally load-bearing idea is that matching a rider to a driver is a genuine race for a single, physical, exclusive resource — two riders cannot be assigned the same driver, which forces a real consistency decision in a system that would otherwise lean entirely eventual.

B. Difficulty

Advanced + a genuinely distinct sub-domain (geospatial indexing) that most candidates haven't touched in other systems. Similar in spirit to Chat System in that there's no purely "naive but functional" V1 — even the most basic version requires picking a way to index location, because a naive full-scan-and-filter approach (P below) fails obviously and immediately, not just at scale.

C. What This Problem Tests

D. Requirements

Functional

  1. A rider can request a trip from a pickup location to a destination and get matched with a nearby available driver
  2. A driver can accept or decline a match offer; on decline (or timeout), the system retries with the next-best driver
  3. Both rider and driver can see the other's live location during an active trip
  4. Drivers continuously report their current location while online

Non-Functional

Out of scope: payments, pricing computation itself (surge is discussed at a conceptual level only, S), driver background checks/onboarding.

E. Clarifying Questions

#QuestionWhy It MattersDesign Decision Affected
1How precise does "nearby" need to be — city-block precision, or exact routing distance?Determines whether straight-line geospatial search is sufficient or a routing-engine ETA is requiredDeep Dive 3 (O)
2How often do drivers report location, and does the server need history or just the latest point?Confirms "overwrite" rather than "append" is the right modelDeep Dive 2 (N)
3What happens if the top-matched driver doesn't respond?Requires a retry/timeout policy, not just a single match-and-done stepDeep Dive 5 (Q)
4Can two riders ever be offered the same driver simultaneously?If yes, atomic assignment (P) is mandatory, not optionalDeep Dive 4 (P)
5Is dynamic pricing (surge) in scope?A supply/demand-driven pricing layer is a substantial separate sub-systemDeep Dive 7 (S)
6Dense urban areas only, or also sparse rural regions?Affects whether a fixed-grid geospatial scheme is efficient everywhere or needs to be adaptiveGeospatial Index (M)

F. Assumptions

G. Capacity Estimation

MetricValue / CalculationWhy It Matters
Concurrent online drivers5M (illustrative, global peak)Base input for location-ingestion load
Location update interval~4 seconds/driverSets the update frequency per driver
Location update QPS5,000,000 ÷ 4 ≈ ~1.25M updates/secThe dominant write load in the entire system — far larger than trip-request volume itself
Trip requests/sec~50M trips/day ÷ 86,400s ≈ ~600/sec average (peak several thousand/sec in dense metro + surge windows)Small relative to location-update volume — confirms location ingestion, not matching itself, is the throughput-critical path
Matching latency budget<10 seconds end-to-end (request → accepted match)Bounds how exhaustive the candidate search + offer/retry cycle (Q) can be
Geospatial cell size (illustrative, urban)An H3/S2 cell edge length on the order of ~100-500m in dense areasFine enough that a handful of cells around a rider covers the realistic driver search radius without scanning irrelevant, far-away drivers
Location data footprint5M drivers × ~100 bytes (lat/lng/timestamp/driver_id) ≈ ~500MB live state (overwritten continuously, not accumulated — N)Tiny in memory terms — confirms the challenge is update throughput, not storage volume

H. Core Entities

EntityPurposeKey Fields
DriverA driver accountdriver_id, status (available/on-trip/offline), current_location (latest only, N)
RiderA rider accountrider_id
TripRequestA rider's ask for a matchrequest_id, rider_id, pickup_location, destination, status (searching/matched/expired)
TripA confirmed, in-progress or completed ridetrip_id, driver_id, rider_id, status, route, timestamps
MatchOffer (ephemeral)One outstanding offer to one driver for one requestrequest_id, driver_id, expires_at — drives the retry logic in Q

I. API Design

POST /v1/trip-requests          { rider_id, pickup, destination } -> { request_id }
GET  /v1/trip-requests/{id}     -> { status, driver_info? }
POST /v1/drivers/location       { driver_id, lat, lng } -> 200          (called every ~4s, N)
POST /v1/match-offers/{id}/respond   { accept: bool }                   (driver's response, Q)
GET  /v1/trips/{id}/location    -> { lat, lng, eta }                    (polled or WebSocket, R)

Why the match offer is a distinct resource from the trip request itself: a single trip request can generate a sequence of offers to different drivers if earlier ones decline or time out (Q) — modeling the offer separately from the request lets that retry loop be expressed cleanly as "create a new offer" rather than mutating the request's state machine in place for every attempt.

J. Basic / Naive High-Level Design (V1)

The simplest thing that satisfies the functional requirement in isolation: store every driver's last known location in a normal relational table, and on a trip request, scan for drivers within a bounding box using a plain WHERE lat BETWEEN... AND lng BETWEEN... query.

flowchart LR R(["Rider requests trip"]) --> API["Matching Service"] API -- "SELECT * FROM drivers WHERE lat/lng in range" --> DB[("Driver Location Table
plain relational")] DB --> API API -- "pick nearest, assign" --> D(["Driver"])

V1 — a plain range scan over lat/lng columns. Correct in a small dataset; catastrophic at real scale.

Why this breaks immediately, not eventually: a standard B-tree index on lat and lng independently cannot efficiently answer a 2D "near this point" query — indexing each dimension separately still forces a scan across a huge, non-contiguous set of rows once the table holds millions of drivers (a B-tree's ordering only helps one dimension at a time). This isn't a "gets slow at scale" problem, it's a wrong-tool-for-the-job problem from the very first realistic dataset size — exactly why M's specialized geospatial index isn't an optimization, it's the actual solution to the actual query.

K. Data Model & Store Choice

Live driver location: an in-memory geospatial index (M) — e.g. Redis with its native GEOADD/GEOSEARCH commands (internally a geohash-sorted set), or a custom in-memory quadtree/S2/H3-bucketed structure — chosen for the same reason Rate Limiter chose Redis for counters: the access pattern (extremely frequent overwrite + frequent proximity read) matches an in-memory store's strengths, and losing the very latest location on a restart is harmless (the next ~4-second update replaces it, N).

Trips, trip requests, driver/rider profiles: a standard relational or wide-column durable store — this is the data that must survive restarts and support the request/accept/complete state machine (Q), a fundamentally different durability requirement than the ephemeral location index.

L. End-to-End Flow — Trip Request to Match

sequenceDiagram participant Rider participant MS as Matching Service participant Geo as Geospatial Index participant Driver Rider->>MS: POST trip-request(pickup, destination) MS->>Geo: search nearby available drivers Geo-->>MS: candidate driver list MS->>MS: rank by ETA (O), pick best candidate MS->>MS: atomic assign attempt (P) MS->>Driver: match offer (push) alt driver accepts Driver-->>MS: accept MS-->>Rider: matched! driver info else driver declines/times out MS->>MS: retry with next candidate (Q) end

M. Deep Dive 1 — The Geospatial Index

Requirement this addresses: directly fixes V1's fatal flaw (J) — efficient "who is near this point" queries are the foundation everything else in this problem is built on.

ApproachHow It WorksStrengthWeakness
GeohashEncodes lat/lng into a single sortable string; shared prefixes mean spatial proximity (mostly)Simple, works natively with a sorted-set store like RedisEdge/boundary cases — two physically close points can have very different geohash prefixes if they straddle a boundary
QuadtreeRecursively subdivides space into 4 quadrants, denser subdivision where points are denserNaturally adapts cell size to driver density (small cells in cities, large cells in rural areas)More complex to shard/distribute than a flat hash scheme
Google S2Projects the sphere onto a cube, subdivides into a hierarchy of cells, uses Hilbert-curve ordering for localityHandles the whole globe (including poles/edge cases geohash struggles with) correctlySteeper conceptual learning curve than geohash
Uber's H3 (real-world answer, and this document's pick)Hexagonal hierarchical grid — hexagons have uniform adjacency (all 6 neighbors are equidistant, unlike a square grid's diagonal-vs-edge distance discrepancy)Purpose-built by Uber specifically for ride-matching-style neighbor queries; open-sourced and battle-tested at Uber's own scaleA less commonly known structure than geohash — worth knowing it's Uber's own real system, not a hypothetical

Why hexagonal cells specifically solve a real problem square grids have: in a square grid, the four diagonal neighbors are ~41% farther away than the four edge-adjacent neighbors, distorting "nearby" queries right at cell boundaries; every one of a hexagon's six neighbors is equidistant, making neighbor expansion (checking one ring further out if too few drivers are found) uniform and predictable — precisely the operation the matching flow (L) performs repeatedly.

Recommendation: H3 as the specific, real-world-grounded answer (it's literally Uber's own open-sourced library for exactly this problem) — geohash is an entirely acceptable, simpler alternative to name if H3's specifics aren't recalled, as long as its boundary-distortion weakness is acknowledged.

N. Deep Dive 2 — High-Frequency Location Ingestion

Requirement this addresses: the ~1.25M updates/sec load from G — by far the largest sustained throughput number in this entire design, dwarfing trip-request volume.

Why "overwrite," not "append," is the correct model: per F's assumption, only the driver's current position matters for matching — there's no requirement to reconstruct a driver's full location history for the matching use case. This means each update is a write to the same key (driver_id's cell/coordinate in the geospatial index, K) rather than an ever-growing log — the exact opposite lifecycle decision from a chat message (Chat System, K) or a post (News Feed, K), both of which must be durably retained forever. Recognizing which category a given piece of data falls into — durable history vs. disposable latest-state — is itself a meaningful piece of design judgment.

Why this still needs careful scaling despite being "just an overwrite": 1.25M overwrites/sec, each also potentially requiring a geospatial index update (moving a driver from one H3 cell to a neighboring one as they drive), must be sharded across many index instances/partitions — the same consistent-hashing-style sharding reasoning from Rate Limiter's scaling deep dive, here partitioned by geographic region/cell rather than by client ID.

A cheap but meaningful optimization: if a driver's reported position hasn't moved out of their current geospatial cell since the last update, the index update itself can be skipped (only the raw lat/lng is refreshed) — cutting a meaningful fraction of the expensive index-restructuring work without affecting matching correctness at all, since matching only cares about which cell a driver is in.

O. Deep Dive 3 — Matching & Ranking Candidates

Requirement this addresses: the <10 second matching-latency NFR (D) — the geospatial search (M) finds candidates; this decides which candidate is actually best.

Why straight-line distance alone is an incomplete ranking signal: the nearest driver by straight-line distance might be on the opposite side of a river, a highway median, or one-way streets that make their actual driving ETA much worse than a driver who's slightly farther in a straight line but has a clear route. A full solution refines the geospatial candidate set (a cheap, coarse first pass) with a call to a routing/ETA engine for the top few candidates only — expensive per-call, so deliberately not run against every driver in the city, only the short list M's index already narrowed things down to.

Why this two-stage funnel (coarse geospatial filter → expensive ETA refinement on a short list), not one or the other alone: pure geospatial ranking is cheap but occasionally wrong in exactly the way described above; pure ETA-engine ranking (calling the routing engine against every driver in a city) is accurate but far too expensive to run at 600+ requests/sec against potentially thousands of drivers each. The funnel gets the accuracy of the expensive computation at a small fraction of its cost by only ever running it against a handful of pre-filtered candidates.

P. Deep Dive 4 — Atomic Driver Assignment

Requirement this addresses: the strong-consistency NFR from D — this is the one place in the entire design where "eventually consistent" is explicitly not good enough, and the single highest-signal deep dive in this problem.

The race, explicitly: two riders' requests can both identify the same driver as their best candidate within the same few-hundred-millisecond window (especially in a sparse area with few available drivers). If both matching attempts read "driver is available" and both then proceed to send an offer, the driver could end up double-booked — the same check-then-act race named explicitly in Rate Limiter's atomicity deep dive (N) and the LLD Parking Lot's last-spot race, here with a human being as the contested resource instead of a counter or a parking spot.

The fix — an atomic compare-and-swap (CAS) on the driver's status, executed as a single atomic operation (e.g. a Redis WATCH/MULTI transaction, an atomic Lua script analogous to Rate Limiter's N, or a conditional update in a store that supports it natively): "set driver's status to pending_offer for request X, only if it is currently available." Whichever matching attempt's CAS lands first wins the driver; the loser's CAS fails cleanly and immediately falls through to the next-best candidate (Q) — no separate locking service required, and no window where both attempts believe they've succeeded.

Why this is the narrow exception to an otherwise eventually-consistent system: location updates (N), ETA ranking (O), and trip tracking (R) all tolerate staleness measured in seconds with no real product harm — but "two riders assigned the same driver" is a directly visible, trust-breaking product failure with no acceptable staleness window at all. Naming this contrast explicitly — this design is eventually consistent everywhere except this one atomic step — is a stronger answer than either "everything is eventually consistent" (misses this) or "everything needs strong consistency" (needlessly slow and complex for location/ETA data that doesn't need it).

Q. Deep Dive 5 — Match Offer Reliability & Retries

Requirement this addresses: the accept/decline/timeout functional requirement (D) — what happens after P's atomic assignment succeeds but the driver doesn't actually confirm.

flowchart TB Offer["Offer sent to
top-ranked driver (O, P)"] --> Wait{"Driver responds
within timeout?"} Wait -- "accepts" --> Done["Trip confirmed"] Wait -- "declines / times out" --> Release["Atomically release driver
back to available (P)"] Release --> Next["Re-run ranking (O) against
remaining candidates"] Next --> Offer

Why this needs a durable, retry-aware workflow rather than a single synchronous function call: the full request→match→accept sequence can span multiple offer/decline cycles, each with its own timeout, potentially taking several seconds — exactly the kind of long-running, multi-step, failure-prone process that a durable workflow orchestration engine (e.g. Temporal, or an equivalent state-machine-as-infrastructure approach) is built for, rather than a single request handler holding state in memory for the whole sequence. If the matching service instance handling a request crashes mid-retry-loop, a durable workflow resumes exactly where it left off; a plain in-memory retry loop would lose all of that state and leave the rider stuck with no match and no explanation.

R. Deep Dive 6 — Live Trip Tracking

Requirement this addresses: the live-location-sharing functional requirement (D) — a distinct problem from N's ingestion, since this is about broadcasting one driver's position to specifically the matched rider (and vice versa) throughout an active trip.

Why this rhymes with the Chat System's connection/routing problem: once matched, the rider and driver need low-latency, bidirectional updates — the same WebSocket-based push model from Chat System's connection tier (M), with the "who is this update for" question answered trivially here (the trip ID directly identifies the other party), unlike chat's harder cross-server user routing problem. Location pushes during a trip reuse N's already-updated position data — no separate ingestion pipeline is needed, just a subscription from the trip's WebSocket channel to the same location stream.

Why polling is a reasonable, simpler fallback here specifically (unlike Chat System, where it was ruled out entirely): a rider watching a driver's dot move tolerates a few seconds of staleness far more gracefully than a chat message would — so a lower-frequency poll (matching N's own ~4-second update cadence) is a defensible, simpler alternative to a full WebSocket push for this one feature, even though WebSockets remain the better choice if the product wants smoother, more frequent visual updates.

S. Deep Dive 7 — Supply/Demand Imbalance (Surge, Conceptual)

Discussed at the level this interview typically expects — the conceptual shape of the problem and how it plugs into the existing design, not a full pricing-engine derivation.

Why this is a natural extension of the existing geospatial structure, not a new system: the same geospatial cells already used for matching (M) are the natural unit for measuring local supply (available drivers per cell) versus demand (open trip requests per cell) — a simple ratio per cell, recomputed on a short interval, is enough to identify an imbalanced area without building new geospatial machinery. A high demand:supply ratio in a cell is both the trigger for surge pricing (out of scope in detail, per D) and a useful signal to widen the matching search radius for requests originating in that cell (a direct, in-scope tie-back to M's neighbor-expansion behavior).

T. Full Solution at Scale — Final Architecture

flowchart TB Driver(["Driver app"]) -- "location ping ~4s (N)" --> Ingest["Location Ingestion Service"] Ingest --> GeoIdx[("Geospatial Index (M)
H3-bucketed, sharded by region")] Rider(["Rider app"]) -- "trip request" --> MS["Matching Service"] MS -- "candidate search" --> GeoIdx MS -- "ETA refine (O)" --> Routing["Routing/ETA Engine"] MS -- "atomic CAS assign (P)" --> DriverStatus[("Driver Status Store")] subgraph WORKFLOW [" Durable Match Workflow (Q) "] direction LR Offer["Offer + retry loop"] end MS --> WORKFLOW WORKFLOW -- "offer" --> Driver Driver -- "accept/decline" --> WORKFLOW WORKFLOW -- "confirmed" --> TripDB[("Trip Store")] TripDB --> Tracking["Live Tracking (R)
WebSocket / poll"] Tracking --> Rider Tracking --> Driver GeoIdx -.-> Surge["Supply/Demand Monitor (S)"]

Everything above is the composition of M through S — no new component introduced here.

U. Design Evolution

What stays stable throughout: the POST /trip-requests and POST /drivers/location contracts from I — every version underneath changes without either app needing to change.

V. Consistency Model

This is the most consistency-bifurcated design in the whole document: location updates (N), ETA ranking (O), and live tracking (R) are all deliberately eventually consistent — a few seconds of staleness in any of them causes no real harm and buys significant scalability. Driver assignment (P) is the one deliberately strongly-consistent island — a single atomic compare-and-swap that must never allow two riders to win the same driver. This is a sharper, more explicit version of Rate Limiter's "eventual everywhere except one atomic step" model (V in that section) — here the stakes of getting the one strong-consistency step wrong are a visibly broken product experience (a driver arriving for the wrong rider), not just a barely-noticeable over-count.

W. Failure Scenarios

X. Bottlenecks & Scaling Boundary

StageActual BottleneckFix Applied
V1 relational range scan2D proximity queries don't map onto a 1D B-tree index at allDedicated geospatial index (M)
Single geospatial index instance~1.25M location updates/sec exceeds any single instance's throughputRegion-based sharding of the index (N)
Sharded index, naive assignmentConcurrent matching attempts can double-book a driverAtomic CAS-based assignment (P)
Atomic assignment, in-memory retry loopA crashed matching-service instance loses in-progress match stateDurable workflow orchestration (Q)

Y. Design Trade-Offs

DecisionOption AOption BTrade-off
Geospatial structureGeohash (simple)H3 hexagonal grid (chosen)Geohash is simpler and works natively with sorted-set stores; H3's uniform hexagon adjacency avoids the diagonal-distance distortion of any square/geohash-style grid
Location data lifecycleAppend full historyOverwrite latest-only (chosen)History enables analytics/replay; overwrite is far cheaper and is all matching actually needs, per F's assumption
RankingPure geospatial distanceTwo-stage geospatial filter + ETA refinement (chosen)Pure distance is cheap but occasionally wrong (rivers/highways); the two-stage funnel gets ETA accuracy at a fraction of calling the routing engine for every candidate
Assignment consistencyEventually consistent (like the rest of the system)Strongly consistent atomic CAS (chosen)Eventual consistency here directly causes double-booking, a visible product failure with no acceptable staleness window — this is the one place the extra coordination cost is mandatory
Retry-loop architectureIn-memory retry logic in the request handlerDurable workflow orchestration (chosen)In-memory is simpler but loses all state on a crash mid-retry; a durable workflow survives failures at the cost of an added orchestration dependency

Z. Common Bad Designs

AA. Interviewer Follow-Ups

Follow-UpDesign ChangeComponents ChangedComponents Unchanged
"What if no drivers are available nearby at all?"Expand the search to the next ring of neighboring geospatial cells (H3's uniform-neighbor property makes this a clean, predictable expansion) before failing the requestSearch radius logic in M/OAssignment, workflow, tracking
"Two riders in the same area request at the exact same millisecond."Directly exercises P's atomic CAS — one wins outright, the other's CAS fails and falls through to its next candidateNone — this is exactly what P already handlesEverything
"Add pooled/shared rides (multiple riders, one driver, overlapping routes)."The matching step now needs to consider route-overlap compatibility, not just proximity — a meaningfully harder ranking problem layered on the same geospatial foundationRanking logic in OGeospatial index, assignment atomicity, tracking
"A driver's app loses connectivity mid-trip."Last-known-location is shown with a staleness indicator rather than treating the gap as an error; matching/assignment for other riders is unaffected since this driver is already on-tripTracking UI/degradation logic (R)Matching, assignment
"How would you detect and price for a supply/demand imbalance?"Directly extends S's per-cell ratio monitoring into an actual pricing multiplier — conceptually straightforward on top of the existing structure, though a full pricing model is its own deep problemNew pricing-signal consumer of the existing per-cell metricMatching, assignment, tracking

AB. Interviewer Evaluation Rubric

AC. Interview Simulation

Interviewer: Design a ride-sharing system — riders request trips, get matched to nearby drivers, and can track them live.
Candidate: The first real problem is finding "nearby" drivers efficiently. A plain SQL query filtering on lat/lng ranges doesn't work well — a standard index only helps one dimension at a time, so at real scale it turns into a huge scan. I'd use a dedicated geospatial index — something like Uber's own H3 hexagonal grid, or geohash as a simpler alternative — so a proximity search is a fast lookup against a small number of nearby cells.
Interviewer: Why hexagons specifically, not a square grid?
Candidate: In a square grid, the diagonal neighbors are farther away than the edge neighbors, so "expand my search by one ring" behaves inconsistently depending on direction. Every neighbor of a hexagon is the same distance away, so expanding the search radius is uniform and predictable.
Interviewer: Drivers are reporting location constantly — how do you handle that volume?
Candidate: Each update overwrites that driver's current position rather than appending to a history — matching only cares where a driver is right now, not where they've been. That keeps the storage footprint tiny regardless of update frequency; the real cost is just making sure the update throughput itself is sharded, by region, across enough index instances.
Interviewer: Now — two riders' requests both pick the same driver as their best match, at nearly the same time. What happens?
Candidate: That's the one place in this whole design where I actually need strong consistency. I'd do the assignment as an atomic compare-and-swap — set the driver's status to "pending offer for this request," but only if it's currently "available." Whichever request's CAS lands first wins the driver; the other one's CAS just fails and falls through to its next-best candidate. Everything else in this system — location updates, ETA estimates — tolerates being a little stale, but assignment can't, because two riders getting the same driver is a real, visible failure.
Interviewer: What if the driver just doesn't respond to the offer?
Candidate: That whole request→offer→accept-or-retry sequence can span several seconds and multiple attempts, so I wouldn't model it as one synchronous function call holding state in memory — if that process crashed partway through, the rider would just be stuck with no explanation. I'd use a durable workflow so the retry loop survives a crash and resumes exactly where it left off, releasing the unresponsive driver back to available and trying the next candidate.

AD. Online Solution Comparison

SourceApproachStrengthWeaknessWorth Learning
Hello InterviewGeospatial-index-first framing, explicit atomic-assignment race discussion, staff-level durable-workflow recommendationClearest articulation of the double-booking race and why it's the one strong-consistency island — the core structure adopted for this sectionDoesn't go deep into the specific H3-vs-geohash-vs-quadtree comparisonThe "eventual everywhere except one atomic step" framing, directly reused from Rate Limiter's own consistency model in this document
ByteByteGoDetailed comparison of geospatial indexing approaches (geohash, quadtree) and the two-stage ranking funnelMost rigorous side-by-side geospatial-structure comparison of any source reviewedLighter on the durable-workflow angle for offer retriesThe geohash/quadtree trade-off table adapted into M
uber/h3 (GitHub, official)The actual, real-world, open-sourced hexagonal geospatial indexing library Uber itself usesGround-truth confirmation that H3 is a real production system, not a hypothetical interview answerA code/API reference, not a system-design walkthrough — needs translation into interview framing, which M providesThe uniform-hexagon-neighbor property, and that this is literally the company's own real infrastructure
dev.to / independent write-ups on Uber-style system design (search-level review)Various community breakdowns covering matching, surge pricing, and trip-state machinesUseful for cross-checking the trip-state-machine shape (H's Trip/TripRequest entities) against independent sourcesInconsistent depth and rigor across different write-ups — treated as supplementary, not primaryConfirms the request/match/accept/trip state progression used in H and L is the standard shape, not an invented simplification

AE. Interview Cheat Sheet

ProblemRide-Sharing (Uber)
DifficultyAdvanced + Geospatial (a genuinely distinct sub-domain from the other four problems)
Core requirementsRequest/match/track a trip; 5M concurrent drivers, ~1.25M location updates/sec, <10s match latency, strong consistency for assignment only
Central decisionA dedicated geospatial index (H3/geohash/quadtree) — a plain relational range scan is the wrong tool from the first realistic dataset size
Location modelOverwrite-latest, not append-history — matching only needs current position
RankingTwo-stage funnel: cheap geospatial filter, then expensive ETA-engine refinement on a short candidate list only
Assignment fixAtomic compare-and-swap on driver status — the one deliberate strong-consistency island in an otherwise eventually-consistent system
Reliability fixDurable workflow orchestration for the multi-step offer/accept/retry sequence, surviving mid-process crashes
Live trackingReuses the same location stream via WebSocket (or a coarser poll, acceptable here unlike Chat System) — no separate ingestion pipeline
Common mistakesPlain lat/lng range scans; treating assignment as eventually consistent; two-call check-then-set races; append-only location history; in-memory-only retry loops
What NOT to over-engineerA full dynamic-pricing/surge engine — name the conceptual tie-in to the existing geospatial cells, don't design a pricing model live