Hiring BarSupport

Design Uber (Ride Hailing & Delivery) — Staff-Level Case Study

Case study66 min read7 diagrams

Technologies referenced in this case study: Redis · Apache Kafka · Cassandra · PostgreSQL · Flink & Stream Processing · ZooKeeper & etcd

Related case studies: Maps & Geospatial · Payment Processing · Real-Time Updates · Stream Processing · Proximity Matching · Notification System

How to Use This Case Study#

Organized for interview use first, reference second. Read once end to end, then drill the sections you're weakest on.

ModeTimeWhat to Read
Quick Review15 minExecutive Summary → Interview Walkthrough → Fault Lines table → Active Drills 1–3
Targeted Study1–2 hrsExecutive Summary → Walkthrough → Section 3 (Fault Lines) → Section 4 (Failures) → Deep Dives 1 and 4
Deep Dive3+ hrsEverything, including the Principal Lens and appendices
What is Ride Hailing & Delivery? — Why interviewers pick this topic

A real-time, two-sided logistics marketplace: riders (or eaters, or shippers) create demand at a point; drivers (or couriers) are mobile supply that must be assigned — not chosen — to that demand within seconds, tracked continuously, priced dynamically, and paid reliably. Uber, Lyft, DoorDash, Grab and Instacart are all variations.

Before vs After — New Year's Eve, 00:05:

Naive design (nearest-driver query, DB row per trip, synchronous payment):
t=0:       Midnight. Trip requests in one city go 40/s → 600/s in 90 seconds.
t=+10s:    Every request runs "SELECT nearest drivers" against the location table,
           which is also taking 60K GPS writes/s. Lock contention on hot rows.
t=+30s:    Two requests are offered the same driver. Both riders see "Driver on the way."
t=+2min:   Card authorizations to the payment processor time out at 3s. Trip creation
           blocks on payment. Request p99 goes from 400ms to 12s. Riders mash retry.
t=+5min:   Duplicate trips, stranded riders, drivers with two pickups. Support queue x40.

Staff design (in-memory location per city cell, leased offers, async payment saga):
t=0:       Same spike.
t=+10s:    GPS writes land in an in-memory, city-sharded index; durable history is async.
t=+30s:    A driver can hold exactly one offer lease (TTL 15s). The second request
           sees the lease and moves to the next candidate.
t=+2min:   Payment pre-auth runs asynchronously with a risk-based fallback; the trip
           proceeds. Surge rises in hot hexes within ~1–2 min and pulls drivers in.
t=+5min:   Completion rate 92% vs 97% baseline. Degraded, not broken. A metric blip.

Why interviewers reach for this question: it has every Staff ingredient in one prompt — a firehose write path (GPS), a contention problem (one driver, many requests), a long-running state machine that crosses money (trip → payment), a control loop that shapes behavior (surge), and a hard multi-region story (a trip cannot "fail over" by being forgotten). Candidates who answer it as "a nearby-drivers query" get leveled Senior.

Mechanics Refresher: Dispatch Strategies
StrategyHow It WorksProsCons
Greedy nearestEach request immediately offered to the closest available driver (by ETA, not distance)Lowest wait-to-offer latency; trivially parallelLocally optimal, globally wasteful; steals drivers from requests 5 s later
Batched matchingCollect requests and drivers for 2–10 s per zone, solve an assignment (Hungarian / min-cost flow)5–15% lower total pickup ETA in dense areas (industry-reported range)Adds the batch window to every request; single solver per zone
Broadcast / first-acceptOffer to N drivers, first to accept winsFast acceptance in sparse areasDriver frustration; race on accept; unfair to slower phones
Sequential offers with leaseOffer to one driver at a time with a 10–15 s timeoutClear single ownership of each offerSlow when acceptance rates are low
Dispatch + chainingAssign a driver who is finishing a trip nearbyReduces idle timeETA uncertainty; rider sees a longer "arriving"

For most production systems: ETA-ranked candidates, batched assignment in dense zones (2–5 s window), greedy in sparse zones, and a sequential offer lease so a driver never holds two offers. The algorithm is rarely the interview question — the location firehose, offer contention, and the trip-to-payment state machine are.


Executive Summary

If you only read one section, read this. Everything else in the case study elaborates on the contrast and the positions below.

What This Interview Actually Tests#

Uber is not a "find the nearest driver" question. A geo index answers that in a millisecond.

It is a real-time control loop over mobile supply, wrapped around a durable state machine that moves money — and the design is about keeping the high-rate, lossy parts (location) from contaminating the low-rate, exact parts (assignment, trip state, payment). It tests:

  • Whether you see that location ingestion (~250K writes/s at 1M online drivers) dwarfs trip requests (~1K/s at peak) by ~250×, and design each path for its own consistency needs
  • Whether you prevent double-assignment of a driver structurally, not with a retry
  • Whether you model the trip as an explicit state machine with a single owner, idempotent transitions and a payment saga
  • Whether you treat surge as a marketplace control system with feedback, guardrails and regulatory owners — not a multiplier column

The key insight: Location is disposable — the next ping arrives in 4 seconds, so losing one costs nothing. A trip transition is not — losing "driver accepted" or "trip completed" strands a person or loses money. Staff engineers give these two data classes completely different storage, replication and failure behavior.

The L5 vs L6 vs L7 Contrast — Start Here#

BehaviorSenior (L5)Staff (L6)Principal (L7)
First moveDraws rider app → API → "Matching Service" → DBSeparates the location firehose from the trip state machine and sizes eachAsks which capabilities (location, dispatch, pricing, payments) are shared across rides, eats, freight — and who owns each platform
LocationWrites every GPS ping to a database tableLatest location in memory per city shard; history streamed async to cold storage; drops are acceptablePrices the ingestion fleet and history retention; sets org-wide location-data retention and privacy policy
DispatchNearest available driver, update status to busyETA-ranked candidates, batched assignment in dense zones, offer lease with TTL so a driver holds one offerTreats dispatch as a marketplace policy engine with experiment governance and driver-fairness metrics
Trip stateA status column updated by whichever serviceExplicit state machine, single owner per trip, versioned transitions, outbox eventsDefines the trip lifecycle as the contract that 20+ downstream teams consume; versioned schema, deprecation policy
PaymentsCharge the card when the trip endsPre-auth at request, capture at completion, idempotency keys, saga with compensations; trip never blocks on paymentOwns the risk policy: who absorbs failed charges, fraud loss budget, driver-payout guarantees
SurgeMultiplier = demand / supplyPer-hex control loop, smoothing, caps, quote locking with TTLSurge as regulated pricing: emergency caps, audit trail, public commitments, legal sign-off
Why "first move" separates levels

L5: Draws the request path first — rider taps, service finds a driver, writes a trip. Everything shares one database. This is a coherent design that collapses under GPS write load: 1M online drivers × 1 ping / 4 s = 250K writes/s into the same store that must do transactional trip updates.

L6: "There are two very different data flows here. Location: ~250K writes/s, each one superseded 4 seconds later, so losing one is free. Trips: ~1K creates/s at peak, but every transition must be exact. I'll build them as separate systems: an in-memory, city-sharded location index, and a durable trip state machine. Dispatch reads from the first and writes to the second."

L7: "Rides, delivery and freight all need location ingestion, ETA and dispatch. I'd want one location platform and one ETA service with product-owned dispatch policies on top, because the ETA model is the single most expensive shared asset and divergent copies will disagree with each other in front of customers."

Why "dispatch" separates levels

L5: "Find the nearest available driver and set their status to busy." Two concurrent requests can read the same "available" driver before either write lands. Adding a transaction on a global drivers table makes it correct and slow.

L6: "Candidates are ranked by ETA, not straight-line distance — a driver 300 m away across a highway can be 8 minutes out. Offering is a lease: SET driver:{id}:offer = trip_id NX PX 15000 on the city shard. A driver holds at most one offer; if they don't accept within 15 s the lease expires and we move on. In dense zones I batch requests for ~2–3 s and solve the assignment jointly."

L7: Recognizes that dispatch objective choices (minimize rider wait vs driver deadhead vs fairness of trip distribution across drivers) are marketplace policy with labor-relations implications, and sets up the governance for changing them.

Why "payments" separates levels

L5: "At trip end, call the payment service to charge the card." If the charge fails or times out, what state is the trip in? Is the driver paid?

L6: "The trip completes regardless of payment. Payment is a saga keyed by trip ID: pre-authorize an estimate plus buffer at request time, capture the final fare at completion with idempotency key trip_id:capture, and if capture fails, retry, then fall back to a secondary method, then to arrears on the rider account. The driver's earnings are credited on completion — the business absorbs collection risk, not the driver."

L7: Sets the risk envelope: acceptable fraud-loss basis points, when to require pre-auth vs allow trusted riders to skip it, and how payouts are guaranteed in markets with different payment rails — owned jointly by Payments, Risk and Finance.

The Staff Positions#

PositionRationale
Location is ephemeral; trips are durableKeep latest location in memory (replicated or not), history async; trips go to a durable, strongly consistent store
Shard everything by city (or region cell)Trips are local; a city is the natural failure and scaling unit; cross-city queries are rare
ETA, not distance, ranks candidatesRoad network and traffic dominate; straight-line distance misranks ~20–30% of candidates in cities
A driver holds at most one offer — enforced by leaseStructural prevention of double-assignment beats detect-and-apologize
Trip is a state machine with one ownerVersioned transitions, idempotent commands, events via outbox; no service writes status directly
Payment never blocks the tripPre-auth at request, capture async; collection risk is a business decision, not a request timeout
Surge is a smoothed control loop with capsUnsmoothed multipliers oscillate; caps and quote locks are regulatory and trust requirements

The Three Intents#

IntentConstraintStrategyFailure ModeCorrectness Bar
On-demand ride hailing (one rider, one driver, now)Time-to-match < ~30 s; pickup ETAIn-memory location, ETA-ranked dispatch with leases, trip state machineDouble assignment, stranded rider, surge oscillationExactly one driver per trip; every transition durable
On-demand delivery (eater, restaurant, courier)Food readiness; three-sided timing; batching multiple ordersDispatch timed to prep-time predictions; order batching; courier assignment can be delayed deliberatelyCold food, courier idle at restaurant, merchant overloadExactly one courier per order; ETA accuracy is the product
Scheduled logistics (freight, grocery windows, reservations)Cost per stop; hours of planning horizonVehicle routing optimization (VRP) in batch; manifestsInfeasible routes, missed windowsPlan feasibility; changes via replanning

🎯 Staff Move: "I'll design on-demand ride hailing, because it has the tightest latency, the sharpest contention on a single driver, and the most direct money path. Then I'll show what changes for delivery — mainly that the right time to dispatch isn't 'now', it's 'when the food will be ready minus the courier's travel time'. Scheduled logistics is a batch optimization problem and I'd scope it out."

The Five Fault Lines#

#Fault LineThe Tension
1Location Freshness vs Ingestion CostPing every 1 s for accuracy, or every 4–5 s for battery, bandwidth and 4–5× cheaper ingestion? Durable or in-memory?
2Greedy vs Batched DispatchOffer immediately (lowest latency) or wait 2–5 s to optimize globally (lower total ETA)?
3Assignment Consistency: Lease vs Transaction vs Detect-and-RepairHow do you guarantee a driver is never assigned twice without a global lock?
4Surge: Market Efficiency vs Fairness and StabilityPrice fast enough to clear the market without oscillation, gouging headlines or regulatory breach
5Trip State Durability vs Availability (and the Payment Handoff)A trip must survive regional failure; payment must be exact; neither may block the rider

In the Wild: Real Production Systems#

Why this section belongs here: these are publicly documented, and citing them shows you know how the real systems evolved.

Uber — H3 and Ringpop-Era Dispatch#

Uber open-sourced H3, a hexagonal hierarchical spatial index, and has described using it for pricing and marketplace analysis: hexagons have uniform neighbor distances, which makes supply/demand aggregation and smoothing across neighbors clean. Its earlier dispatch system (publicly described around 2015–2016) sharded state across nodes using Ringpop, a consistent-hashing library with SWIM gossip membership, so each node owned a slice of the geo space.

Staff insight: The shard key for dispatch is geography because the workload is geographic — a request only ever needs drivers within a few kilometers. Say "city/cell is the unit of sharding and of failure" and you've made the most important architecture decision in one sentence.

Uber — Fulfillment Platform Re-architecture on Spanner#

Uber's engineering blog described rebuilding its fulfillment platform — the system that models trips, orders and the entities that fulfill them — onto Google Cloud Spanner to get strongly consistent, transactional updates across entities while scaling horizontally, replacing an earlier design that relied on in-memory state with application-level consistency.

Staff insight: At some scale, "eventually consistent trip state + reconciliation" costs more in engineering and incident time than paying for a strongly consistent store. The trip/order state machine is the one place a transactional store is worth its price.

DoorDash — Dispatch Timed to Food Readiness#

DoorDash has written publicly about its dispatch system (known internally as DeepRed), which uses ML predictions for food prep time and travel time and solves assignment as an optimization problem, including deciding when to dispatch and whether to batch multiple orders onto one courier.

Staff insight: Delivery flips the objective. For rides, dispatching instantly is almost always right. For food, dispatching too early wastes courier time at the restaurant and dispatching too late gives cold food. Mentioning this shows you've understood intents, not just architectures.

What Interviewers Probe#

After You Say...They Will Ask...(What They're Evaluating)
"Drivers send their location every few seconds""How many writes per second? Where do they go? What if you lose some?"Sizing and data classification
"Find the nearest driver""Nearest by what? Two riders request at once — can they get the same driver?"ETA awareness, contention
"Update the trip status""Who is allowed to update it? What if the driver app and the rider app disagree?"State machine ownership
"Charge the card at the end""The charge times out. Is the trip complete? Is the driver paid?"Saga, idempotency, who absorbs risk
"Surge = demand/supply""It flips between 1.0× and 3.0× every minute. Why, and how do you fix it?"Control-loop thinking
"Multi-region for availability""A region dies mid-trip. What happens to the 200K active trips?"Durable state placement

System Architecture Overview#

Diagram: System Architecture Overview

Reading the diagram: three planes with three different consistency contracts. The location plane is in-memory and lossy on purpose — a lost ping is replaced in 4 s. The dispatch plane is per-city and uses short leases so a driver holds one offer at a time. The trip plane is the only durable, strongly consistent store; every transition writes to it and emits an event through the outbox. Payment and surge are consumers of events, never synchronous dependencies of the rider's request.

Quick-Reference: The 30-Second Cheat Sheet#

TopicThe L5 AnswerThe L6 Answer — Say This
Location"Write GPS to the drivers table""Latest position in memory, sharded by city; history async to object storage. A lost ping is free."
Nearby search"Geohash query for nearest drivers""H3 k-ring around the pickup for candidates, then rank by ETA from the routing service."
Assignment"Mark driver busy in a transaction""Offer lease SET NX PX 15000 per driver; one offer at a time; batch in dense zones."
Trip"Status column""State machine, single owner, versioned transitions, outbox events."
Payment"Charge at end""Pre-auth at request, capture at completion, idempotency key per step, trip never blocks on payment."
Surge"demand / supply""Per-hex control loop every ~60–120 s, smoothed across neighbors, capped, quote locked for a few minutes."
Multi-region"Active-active everything""City pinned to a home region; trip store replicated synchronously within region, async across; location rebuilt from pings in 4 s after failover."

Key Numbers Worth Memorizing#

MetricValueWhy It Matters
Driver GPS ping interval~4 s (commonly cited for Uber's driver app)Sets ingestion rate and freshness bound
Online drivers at global peak (assumption)~1M→ ~250K location writes/s
Ping payload~100–200 bytes~25–50 MB/s ingest; ~2–4 TB/day of raw history
Trips per day (Uber-scale, public order of magnitude incl. delivery)~25–30M~300/s average, ~1K/s peak, ~5–10× spikes on New Year's Eve
Location : trip-request write ratio~250 : 1Why they must be separate systems
Offer timeout / lease TTL10–15 sBounds how long a driver is "held"
Batch window (dense zones)2–5 sAdds latency; buys global efficiency
H3 res 8 / res 9 hex~0.74 km² / ~0.11 km² (edge ~460 m / ~175 m)Candidate search and surge granularity
GPS accuracy~5–10 m open sky; 30–100 m+ in urban canyonsMap-matching needed; don't trust raw points for fares
Surge recompute cadence~1–2 min per hexFaster oscillates; slower misses the spike
Price quote lock~2–5 minHonoring what the rider saw
Trip request → driver assigned p50< ~10–30 s in healthy marketsThe product's first promise
Pre-auth holdestimate + ~10–20% bufferCovers route deviations without re-auth

Interview Walkthrough

The most common mistake: candidates spend 15 minutes on geohash vs quadtree for "nearby drivers" and never reach the trip state machine or payments. The geo index is two sentences. The phases below keep it that way.


Phase 1: Requirements & Framing (2–3 min)#

Functional core in one breath:

"Riders request a ride from A to B, see a price, get matched to a nearby driver, track the driver, take the trip, and pay. Drivers go online, stream location, receive offers, accept, and get paid."

Then the non-functionals, with numbers:

"Let me size the two flows separately. Assume ~1M drivers online at peak, pinging every 4 seconds — that's ~250K location writes/s. Trips are ~25M/day, ~1K requests/s at peak, with New Year's-style spikes of 5–10× in a single city. So location is a ~250:1 firehose relative to trips. Location is disposable — the next ping replaces it. Trip transitions are not — they're money and safety."

Commit to the intent and constraints:

"I'll design on-demand rides. Targets: request-to-assignment p50 under ~15 s, a driver is never assigned to two trips, every trip transition is durable, payment never blocks the ride. I'll come back to what changes for food delivery."

🎯 Staff Move: Naming the 250:1 ratio in the first two minutes tells the interviewer you won't put GPS pings in the trip database — and buys you permission to skip the naive design entirely.


Phase 2: Core Entities & API (1–2 min)#

  • DriverLocation: driver_id, lat/lng, heading, speed, ts, h3_cell — latest only, in memory
  • DriverState: driver_id, status ∈ {offline, available, offered, en_route, on_trip}, current_trip_id, vehicle_class
  • Trip: trip_id, rider_id, driver_id?, state, version, pickup, dropoff, quote_id, fare, timestamps per transition
  • Quote: quote_id, price, surge_multiplier, expires_at (~2–5 min)
  • PaymentIntent: trip_id, auth_id, amount_authorized, amount_captured, state
POST /v1/quotes        {pickup, dropoff, product}        → {quote_id, price, surge, expires_at}
POST /v1/trips         {quote_id, idempotency_key}       → {trip_id, state: MATCHING}
GET  /v1/trips/{id}                                      → {state, driver, eta, version}
POST /v1/trips/{id}/cancel {version}                      → {state, cancel_fee?}

Driver socket (bidirectional, persistent):
  ← location {lat, lng, heading, speed, ts}               every ~4 s
  → offer {trip_id, pickup, eta, expires_in: 15}
  ← offer_response {trip_id, accept|decline}
  ← trip_action {trip_id, action: arrived|start|complete, version}

🎯 Staff Move: "Every trip mutation carries a version, and creation carries an idempotency key. Mobile clients retry constantly — a rider double-tapping 'Request' in a tunnel must create one trip, and a driver tapping 'Complete' twice must complete it once."


Phase 3: High-Level Architecture (≤ 5 min)#

Diagram: Phase 3: High-Level Architecture (≤ 5 min)

Three sentences:

"Location ingest writes latest positions to an in-memory index sharded by city — no database on that path. A trip request creates a durable trip in MATCHING and asks the city's dispatcher for a driver; dispatch pulls H3-ring candidates, ranks by ETA, and offers under a 15-second lease. Every trip transition commits to the trip store with an outbox row, and payment, surge, receipts and analytics consume those events asynchronously."


Phase 4: Transition to Depth#

"The nearby search is an H3 k-ring lookup — I don't think that's where the risk is. The three places I'd want to go deep are: preventing double-assignment under a request spike, the trip state machine and how it hands off to payments, and surge as a control loop. I'd suggest starting with double-assignment since it's the failure riders see first."


Phase 5: Deep Dives (25–30 min)#

Deep DiveTimeWhat You Must Land
Dispatch & double-assignment7–9 minETA ranking, lease per driver, batching in dense zones, accept path is a conditional state transition
Trip state machine & payment handoff7–9 minStates, single owner, versioned transitions, outbox, pre-auth/capture saga, who absorbs failure
Location ingestion4–5 minIn-memory latest, async history, city sharding, reconnection storms
Surge4–6 minPer-hex supply/demand, smoothing, caps, quote lock
Multi-region3–4 minCity home region, trip store replication, location rebuild

Double-assignment, as you'd say it:

"Two requests 200 ms apart in the same block both see driver D as the best candidate. Dispatch for each does SET offer:D trip_1 NX PX 15000 on the city's lease store; only one succeeds. The loser takes its next candidate. When D accepts, the Trip Service does a conditional transition: trip_1 from MATCHING to DRIVER_ASSIGNED if the version matches and the lease still names trip_1. If D accepts after the lease expired and trip_1 was reassigned, the accept is rejected and D sees 'offer expired'. At no point can D hold two trips."


Phase 6: Wrap-Up (2–3 min)#

"To summarize: location is an in-memory, lossy, city-sharded firehose; dispatch holds one lease per driver; the trip is a durable state machine with an outbox; payment is a saga that never blocks the ride. What I'd monitor first: double-assignment count (should be zero), assignment latency p50/p99 per city, and payment capture failure rate. What I'd build next: batched matching in the top 20 cities and delivery-specific dispatch timing. What I skipped: route optimization for scheduled logistics and the ETA model itself."


Common Timing Mistakes#

MistakeTime LostFix
Designing a geo index from scratch8–12 min"H3 k-ring, in memory, per city."
Designing maps / routing10+ min"ETA is a service with a road graph and live traffic; I'll treat it as a dependency."
Rider and driver apps' UI flows5 minName the state machine instead; the UI renders states
Payment processor internals8 min"Payment is its own design — here's the handoff contract." See Payment Processing
No numbers until minute 20Whole interview250K/s vs 1K/s in Phase 1

1. The Staff Lens#

1.1 Why This Problem Exists in Staff Interviews#

Ride hailing is the canonical "mixed-criticality" system: the same user action touches data that can be lost freely and data that can never be lost. Interviewers use it to see whether you classify data before storing it.

Data ClassRateLoss ToleranceConsistencyStorageWho Notices Loss
Driver location~250K/sHigh (next ping in 4 s)None neededIn-memory, per cityNobody
Offer / lease~1–5K/sMedium (expires in 15 s)Single-key atomicIn-memory with TTLDriver (missed offer)
Trip transitions~5–10K/sZeroLinearizable per tripDurable, replicatedRider, driver, finance
Payment steps~1–2K/sZero, and idempotentExactly-once effectPayment ledgerFinance, auditors
Surge multipliers~1 per hex per minMedium (stale by 1–2 min)Versioned snapshotsCache + logRider (price), regulator

A Senior design that puts all five in one database is overpaying for location and underprotecting payments at the same time.

1.2 The L5 vs L6 Contrast — Visual#

Diagram: 1.2 The L5 vs L6 Contrast — Visual

1.3 The Staff Question That Cuts Through Everything#

"If we lose this write, what happens to a person?"

  • Lose a GPS ping → the map dot jumps 4 s later. Nothing happens. Keep it in memory.
  • Lose an offer → driver misses one request; it's re-offered. Short TTL, no durability.
  • Lose "driver accepted" → rider waits for a driver who thinks they're assigned, or two drivers show up. Durable, conditional.
  • Lose "trip completed" → rider isn't charged, driver isn't paid, driver can't take the next trip. Durable, idempotent, outbox.

🎯 Staff Move: "I sort every write by what happens to a person if it's lost. That sort gives me three storage tiers, and it's the reason GPS pings never touch the trip database."


2. Problem Framing & Intent#

2.1 The Three Intents — Explained#

Intent 1 — On-demand ride hailing. One rider, one driver, now. The rider's patience is ~1–3 minutes before they cancel; the driver's attention per offer is ~10–15 s. The system optimizes pickup ETA and match rate under tight latency, with strict single assignment. Pricing adjusts in near-real time to pull supply toward demand.

Intent 2 — On-demand delivery. Three parties: eater, merchant, courier. The critical constraint is food readiness: dispatching a courier the moment an order is placed wastes courier time at the restaurant (idle courier cost) and dispatching too late delivers cold food. Couriers can carry multiple orders (batching), so assignment is a routing problem with pickup/dropoff precedence. ETA accuracy is the product.

Intent 3 — Scheduled logistics. Grocery windows, freight, school buses. Hours of planning horizon, cost-per-stop objective, vehicle routing optimization solved in batch. Real-time tracking matters; real-time matching doesn't.

DimensionRide HailingDeliveryScheduled Logistics
Dispatch timingImmediatelyPrep-time aware (often delayed 5–15 min)Hours ahead
Assignment unit1 rider ↔ 1 driverN orders ↔ 1 courierMany stops ↔ 1 vehicle route
Primary objectivePickup ETA, match rateDelivered-hot ETA, courier utilizationCost per stop
SolverGreedy or small batch (seconds)Batch with prep predictions (seconds–minutes)VRP optimizer (minutes–hours)
Key failureDouble assignment, stranded riderCold food, courier waitingInfeasible plan

2.2 When NOT to Use This Architecture#

SituationWhy It's WrongUse Instead
Supply chooses demand (freelancers browse jobs)No assignment problem; it's search + contention on acceptMarketplace listing + Flash Sales-style accept
< ~10K concurrent drivers in totalA single Postgres + PostGIS handles location and trips in one place with row locksMonolith, one database, add planes later
Scheduled-only operationsReal-time dispatch adds cost and complexity with no benefitBatch VRP with a manifest service
Mutual choice between peersTwo-sided consent, not assignmentProximity Matching
Fixed routes (buses, shuttles)No matching at all; just trackingLocation ingestion + Real-Time Updates

🎯 Staff Move: "Under ~10K concurrent drivers I'd run this as one service on one Postgres with PostGIS, and pings every 5–10 s. The three-plane architecture is what you grow into when the ping rate stops fitting in a database — I'd name that trigger rather than build for it on day one."

2.3 What the Interviewer Leaves Underspecified#

UnderspecifiedWhy It MattersWhat to Say
ScaleWhether you need planes at all"~1M online drivers at peak, ~25M trips/day."
Ping intervalIngestion cost and freshness"4 s while online, 1–2 s during active trip for tracking smoothness, 30 s when idle-parked."
Products (pool, XL, delivery)Dispatch complexity"UberX first; pool and delivery as extensions."
Pricing modelUpfront vs metered"Upfront quote locked for a few minutes; final fare may adjust for major route changes."
Payment timingWhere risk sits"Pre-auth at request, capture at completion."
RegionsResidency, failover"Each city has a home region."
Cancellation policyState machine branches"Free cancel within ~2 min of assignment; fee after."

2.4 Precise Terminology#

TermMeaning HereCommon Confusion
DispatchChoosing which driver gets an offer for which requestNot the same as "matching" in dating; the platform decides
OfferA time-limited proposal to one driverNot an assignment until accepted and committed
LeaseA TTL'd exclusive claim on a driver for one offerPrevents double-offer; expires automatically
AssignmentCommitted trip transition to DRIVER_ASSIGNEDThe durable fact; the lease is advisory
ETAPredicted travel time on the road network, with trafficNot straight-line distance
DeadheadDriver miles without a passengerThe cost dispatch minimizes alongside rider wait
Surge / dynamic pricingMultiplier on base fare per area and timeNot the same as the upfront quote, which includes it
Quote lockThe window during which the shown price is honoredPrevents bait-and-switch between quote and request
Pre-authorizationA hold on the rider's card for the estimated amountNot a charge; expires after days if not captured
CaptureConverting a hold into a charge for the final amountMust be idempotent
OutboxEvents written in the same transaction as the state change, relayed laterPrevents "state changed but event lost"
Map-matchingSnapping noisy GPS points to road segmentsRequired before computing distance-based fares

3. The Fault Lines#

3.1 Fault Line 1: Location Freshness vs Ingestion Cost#

StrategyWhat WorksWhat BreaksWho Pays
Every ping to a durable DBFull history; simple queries~250K writes/s of mostly superseded data; contends with trip writes; $$$Infra budget; trip latency
Latest in Redis GEO, history to KafkaFast GEOSEARCH; history decoupledRedis cluster per city must absorb bursts on reconnect; single-threaded shards hot in big citiesPlatform team runs many Redis clusters
Latest in the dispatch process's own memory, sharded by city/cell (Staff default at scale)Zero network hop for dispatch reads; state co-located with the consumerProcess restart loses state → rebuilt from pings in one interval (~4 s); needs sticky routing of pings to ownerDispatch team owns routing and rebuild
Adaptive ping rateOnline-idle 15–30 s, available 4 s, on-trip 1–2 sClient complexity; server must tolerate varied freshnessMobile team

The Staff default: latest location in memory, owned by the city/cell shard that dispatches for that area; pings routed by h3_cell → shard through the connection gateway; every ping also appended to Kafka for history, ETA-model training, fraud and fare disputes. Losing a shard's memory costs one ping interval — the ping stream is the replication mechanism.

Adaptive intervals cut ingestion roughly in half: a typical fleet spends ~40–50% of online time idle or parked, where 15–30 s pings are enough.

When to deviate: under ~50K online drivers, Redis GEO per region is simpler and plenty fast (~0.1–1 ms per GEOSEARCH). Move state into the dispatcher only when network hops and Redis hot shards show up in dispatch p99.

🎯 Staff Move: "I don't replicate driver location. The driver's phone replicates it for me every 4 seconds. If a shard dies, its index is rebuilt by the next round of pings — so durability here would be paying for something the protocol already gives us."

3.2 Fault Line 2: Greedy vs Batched Dispatch#

Diagram: 3.2 Fault Line 2: Greedy vs Batched Dispatch

Greedy total pickup ETA: 11 min. Batched: 5 min — at the cost of up to 3 s added to Rider 1's wait.

StrategyWhat WorksWhat BreaksWho Pays
Greedy nearest-ETALowest time-to-offer; trivially parallel per requestGlobally wasteful in dense areas; long tail of bad pickupsRiders with 9-minute pickups; drivers' deadhead
Batched assignment (2–5 s) (Staff default in dense zones)Industry-reported ~5–15% lower total ETA in dense marketsLatency floor of the window; one solver per zone is a hot spot and a SPOFEvery rider pays the window; dispatch team owns the solver
AdaptiveBatch where density is high, greedy where sparseTwo code paths; boundary behaviorDispatch team

The Staff default: adaptive. Per H3 res-7 zone, if pending requests × available drivers exceeds a threshold (e.g., ≥ 3 × 3), run a batch every 2–3 s using min-cost assignment on an ETA matrix; otherwise greedy. The ETA matrix is the expensive part: 50 requests × 200 candidates = 10K ETA lookups per batch — served from a precomputed road-graph routing engine with per-segment traffic, ~sub-ms per lookup.

When to deviate: delivery changes the window entirely — the "batch" is minutes and includes when to dispatch. Airport queues are FIFO by regulation or policy, not ETA — a separate dispatcher.

🎯 Staff Move: "Batching is a policy trade: every rider pays ~2–3 s so that the tail rider doesn't pay 7 minutes. I'd only turn it on where density makes the tail real, and I'd measure p90 pickup ETA, not average."

3.3 Fault Line 3: Assignment Consistency — Lease vs Transaction vs Detect-and-Repair#

The invariant: a driver is attached to at most one active trip, and a trip to at most one driver.

StrategyWhat WorksWhat BreaksWho Pays
Global transaction on drivers + trips tablesObviously correctCross-shard 2PC or a single hot database; tens of ms under contention; spike collapseEveryone at peak
Detect and repair (offer freely, fix conflicts after accept)Fast offersDriver accepts two trips; one rider stranded; apology creditsRiders, support, brand
Offer lease per driver + conditional trip transition (Staff default)Lease prevents double-offer cheaply (SET NX PX); trip transition is the durable commit with a version checkLease store is a dependency; lease and trip can disagree brieflyDispatch team owns lease store; trip team owns the commit
Single-writer dispatcher per zoneNo locks; the zone's dispatcher is the only one that can offer its driversZone boundaries: drivers near an edge; failover of the zone ownerDispatch team owns zone ownership and handoff

The lease is advisory; the trip transition is authoritative:

# Dispatch: claim a driver for an offer
ok = lease_store.set("offer:" + driver_id, trip_id, NX, PX=15000)
if not ok: try next candidate

# Accept: authoritative commit (trip store, per-trip linearizable)
BEGIN
  trip = SELECT ... FROM trips WHERE trip_id = :t FOR UPDATE
  drv  = SELECT ... FROM driver_state WHERE driver_id = :d FOR UPDATE
  if trip.state != 'MATCHING' or drv.status != 'available': ROLLBACK → "offer expired"
  UPDATE trips SET state='DRIVER_ASSIGNED', driver_id=:d, version=version+1
  UPDATE driver_state SET status='en_route', current_trip_id=:t
  INSERT INTO outbox (trip_id, event='driver_assigned', version=...)
COMMIT

Trip and driver state live in the same city-sharded store so this is a single-shard transaction — the reason city sharding matters for correctness, not just scale.

When to deviate: a driver crossing a city boundary mid-shift needs a handoff between shards: the old shard marks the driver migrating, the new shard takes ownership on the next ping, and offers are paused for that driver for one ping interval.

🎯 Staff Move: "The lease stops the obvious race cheaply. But the lease can expire while an accept is in flight, so the accept is a conditional transaction on the trip and driver rows in the same shard. The lease is a hint; the commit is the truth."

3.4 Fault Line 4: Surge — Market Efficiency vs Fairness and Stability#

StrategyWhat WorksWhat BreaksWho Pays
No surge (fixed prices)Simple; no headlinesDemand spikes → no drivers; riders wait 20+ min or get nothingRiders (unavailability), drivers (no incentive)
Raw ratio per hex, every minuteResponsiveOscillation: price up → demand drops, drivers arrive → price crashes → repeat; neighbor hexes differ wildly (walk 100 m, pay 2×)Riders (unpredictability), trust
Smoothed control loop with caps and quote lock (Staff default)Stable; spatially smooth; honors shown priceSlower to react (1–2 min); caps leave some demand unservedBusiness (unserved demand at cap)
Driver incentives instead of rider priceSofter rider experienceSlower supply response; costlierFinance

Surge as a control loop:

every 60–120 s, per H3 res-7 hex h:
  demand   = requests + app_opens_with_quote in last 5 min (h)
  supply   = available drivers + drivers finishing trips within 5 min (h)
  raw      = f(demand / supply)                   # tuned curve, not a ratio
  spatial  = weighted avg of raw over k-ring(h, 1)  # no 2× cliffs between neighbors
  temporal = α · spatial + (1 − α) · previous(h)  # α ≈ 0.3–0.5, damps oscillation
  mult(h)  = clamp(temporal, 1.0, cap(h, time))   # cap from policy; emergency caps
  publish versioned snapshot; quotes embed snapshot version

The Staff point is ownership: the curve f, the cap policy and the emergency override are owned by the Marketplace/Pricing team with Legal sign-off. Uber has publicly committed to capping surge during declared emergencies in some jurisdictions — the kind of external commitment engineering must be able to enforce with a kill switch that works in seconds.

🎯 Staff Move: "Surge is a feedback controller, so I treat it like one: smoothing, damping, bounded output, and a manual override. And the cap table isn't config engineers edit — it's policy with a legal owner."

3.5 Fault Line 5: Trip State Durability vs Availability — and the Payment Handoff#

Diagram: 3.5 Fault Line 5: Trip State Durability vs Availability — and the Payment Handoff
StrategyWhat WorksWhat BreaksWho Pays
Status column, any service writesEasyIllegal transitions (COMPLETED → DRIVER_ASSIGNED); lost updates; no auditSupport, finance reconciliation
State machine in a strongly consistent, city-sharded store with outbox (Staff default)Linearizable per trip; transitions validated; events never lostRegion failover must move the city's store; costlier writesTrip platform team
Event-sourced trip logFull history, replayableRead models lag; complexity for every consumerEvery consuming team
Workflow engine (Cadence/Temporal-style) owns the lifecycleTimers (no-show, matching timeout) and retries are first-classAnother critical dependency; workflow versioningPlatform team

Payment handoff — the saga:

Diagram: 3.5 Fault Line 5: Trip State Durability vs Availability — and the Payment Handoff

Rules that make it safe:

  • Every processor call carries an idempotency key derived from trip_id + step — retries never double-charge. See Payment Processing.
  • Pre-auth failure at request time: for new or risky riders, block the request (fail-closed); for trusted riders, proceed and try again (fail-open) — a risk-policy decision owned by Payments/Risk.
  • Capture failure: retry with backoff over hours; try a backup payment method; move trip to ARREARS and block the rider's next request until settled.
  • Driver earnings are credited at COMPLETED, not SETTLED. The company absorbs collection risk; the driver is never the victim of a rider's card.

🎯 Staff Move: "The trip and the payment are two state machines linked by events. The trip can finish while the payment is still trying. The only coupling is policy: whether a risky rider can start a trip without a successful pre-auth — and that's a Risk team decision, not a timeout I pick."


4. Failure Modes & Operational Reality#

4.1 Double Assignment Under a Spike#

t=0        Stadium concert ends. 3,000 requests in 4 minutes in 6 hexes.
t=+20s     Lease store (one Redis shard for the city) at 100% CPU; SET NX p99 → 400 ms.
t=+40s     A code path added last month falls back to "offer without lease" on lease
           timeout to "keep matching moving."
t=+60s     148 drivers receive two offers; 61 accept both within the window.
t=+3min    61 riders see a driver who is going somewhere else. Cancellations spike.
  • Detection: dispatch.double_offer_total (should be 0), trip.assign_conflict_total (conditional commits rejected), lease.latency_p99.
  • Blast radius: one city, event-driven spikes.
  • Mitigation: remove the fallback — on lease timeout, skip the candidate, never offer unleased; shard the lease store by H3 res-6; admission control on requests per hex (queue with an honest ETA rather than overload).
  • Prevention: invariant test in CI ("no offer without lease"); lease store sized for 10× city peak.
  • Owner: Dispatch team.

4.2 Driver Reconnection Storm#

t=0        A cellular carrier has a 3-minute outage across one metro.
t=+3min    Carrier recovers. 40K drivers reconnect within ~10 s.
t=+3min    Connection gateway TLS handshakes spike 50×; ping backlog replays.
t=+4min    Location index for the metro is 3 minutes stale; dispatch offers drivers
           who have since moved 2 km. Pickup ETAs are wrong by 5+ minutes.
  • Detection: conn.handshakes_per_s, location.staleness_p99_s{city} > 15 s, dispatch.eta_error_p90.
  • Mitigation: clients reconnect with jittered backoff (0–30 s); clients drop queued pings older than 10 s and send only the latest; dispatch excludes drivers with location older than 20 s.
  • Prevention: TLS session resumption; per-city connection gateway capacity for 3× reconnect; staleness filter always on.
  • Owner: Edge/connection platform (reconnect), Dispatch (staleness filter).

4.3 Surge Oscillation#

  • Symptom: a downtown hex flips between 1.0× and 2.8× every 2 minutes; social media screenshots; drivers chase surges that vanish on arrival.
  • Root cause: α = 1.0 (no temporal smoothing) after a config change; no neighbor smoothing on a new city launch.
  • Detection: surge.multiplier_volatility{hex} (std dev over 15 min), surge.neighbor_delta_max.
  • Mitigation: restore smoothing; freeze surge snapshot for the city while investigating.
  • Owner: Pricing team; config change process owned with Marketplace.

4.4 Payment Processor Degradation#

t=0        Card processor p99 goes 300 ms → 8 s in one region.
t=+1min    Pre-auth calls time out; if the request path awaits pre-auth, requests stall.
t=+5min    Capture retries pile up; outbox relay lag grows.
  • Detection: payment.auth_latency_p99, payment.capture_retry_queue_depth, outbox.relay_lag_s.
  • Blast radius: with the saga design — none to trips; collection is delayed. With a synchronous design — every new request in the region.
  • Mitigation: trust-tier policy: trusted riders proceed without completed pre-auth; circuit-break the processor and route to a secondary processor where contracted; capture retries with exponential backoff over hours.
  • Owner: Payments team; risk policy owned by Risk.

4.5 Trip Store Region Failure Mid-Trip#

  • Scenario: the home region for 30 cities loses its trip store primary. ~200K trips are in progress.
  • What must not happen: trips vanish; drivers can't complete; riders are double-charged after recovery.
  • Mitigation: trip store replicated synchronously across zones within the region (survives a zone); for a region loss, promote the async replica in the paired region (RPO of seconds). Clients cache the current trip state and version locally and re-submit their last action with the same idempotency key after failover. Transitions lost in the RPO window are reconstructed from client-held state and location history (e.g., "completed" inferred from driver app + GPS at dropoff).
  • Detection: trip.write_errors{region}, trip.client_resubmits_total.
  • Owner: Trip platform + SRE.

4.6 GPS Drift and Fare Disputes#

  • Symptom: urban canyons produce 50–100 m jumps; naive distance summation inflates fares by 5–15% on some routes.
  • Mitigation: map-matching to road segments before distance calculation; upfront pricing so the fare is mostly fixed at request; disputes resolved from matched route.
  • Owner: Maps/Routing team; Fares team.

4.7 Operational Reality Matrix#

FailureDetection SignalBlast RadiusMitigationOwner
Double assignmentdispatch.double_offer_total > 0One city at spikesSkip unleased candidates; admission controlDispatch
Reconnection stormlocation.staleness_p99_s > 15One metroJittered reconnect; staleness filterEdge platform
Location shard crashlocation.index_size dropOne zone for ~4 sRebuild from next pingsDispatch
Surge oscillationsurge.multiplier_volatilityHexes in one citySmoothing, freeze snapshotPricing
Processor degradationpayment.auth_latency_p99Collection delay onlyTrust tiers, secondary processorPayments
Trip store failovertrip.write_errorsCities homed in regionReplica promotion, client resubmitTrip platform
ETA service downeta.errorsDispatch qualityFallback to haversine × city speed factor (fail-open)Routing
Outbox relay stalledoutbox.relay_lag_s > 60Payments, receipts, surge inputsRestart relay; events are durable in outboxTrip platform
GPS driftfare.dispute_rateIndividual faresMap-matching, upfront pricingMaps / Fares

🎯 Staff Move: "ETA fails open to a crude estimate, location fails to one-ping staleness, payments fail to arrears — but trip transitions never fail open. That's the one place we'd rather reject a request than guess."


5. Evaluation Rubric#

5.1 Level-Based Signals#

DimensionSenior (L5)Staff (L6)Principal (L7)
ScopingRider/driver featuresThree intents; commits to rides; explains how delivery changes dispatch timingIdentifies shared platforms across rides/delivery/freight and product-owned policies
SizingTrips per day250:1 location-to-trip ratio drives plane separationConverts to $/month per plane; knows which plane dominates cost
LocationGPS table + geo indexIn-memory latest per city, async history, pings as replicationOrg retention/privacy policy for location history; data as a shared asset
DispatchNearest driverETA ranking, leases, adaptive batching, conditional commitDispatch objective governance, driver fairness, experimentation platform
StateStatus columnExplicit state machine, single owner, outboxTrip lifecycle as a versioned org contract consumed by 20+ teams
PaymentsCharge at endPre-auth/capture saga, idempotency, trip never blocksRisk envelope, fraud-loss budget, payout guarantees by market
Pricingdemand/supplySmoothed control loop, caps, quote lockSurge as regulated pricing: legal commitments, audit trail, emergency caps
ResilienceReplicasCity-as-failure-unit, region pairing, client resubmitCell architecture, error budgets per plane, regional game days

5.2 Strong Hire Signals#

SignalWhat It Sounds Like
Classifies data by loss tolerance"If we lose a ping, nothing happens. If we lose 'completed', a driver can't take the next trip."
Structural contention fix"A driver holds one lease. The accept is a conditional commit on the same shard."
Explicit state machine"No service writes status. They send commands; the trip service validates transitions."
Payment decoupled with policy"The trip completes regardless. Whether we require pre-auth is a risk policy."
Control-loop surge"Smoothing, damping, caps, override — it's a controller."
Names owners"Pricing owns the curve; Legal owns the caps; Dispatch owns leases."

5.3 Lean No-Hire Signals#

SignalWhy It Misses the Bar
GPS pings written to the trips databaseNo data classification; will not survive scale
"Lock the driver row" with a global databaseCorrect but ignores the throughput and blast radius
Synchronous payment in the request pathCouples rider experience to a third party's latency
Surge as a pure ratioIgnores feedback dynamics and fairness
No cancellation or no-show statesState machine incomplete; real trips hit these constantly
Straight-line distance for candidate rankingMisranks drivers across rivers, highways, one-way grids

5.4 Common False Positives#

  • Geo-index fluency ≠ dispatch design. Knowing H3 resolutions is table stakes.
  • "We'll use Kafka" ≠ event-driven correctness. Without an outbox, state and events diverge.
  • Optimization vocabulary (Hungarian algorithm) ≠ marketplace judgment. The signal is knowing when batching's latency is worth it.
  • Multi-region buzzwords ≠ trip durability. "Active-active" without saying who owns a trip mid-failover is a red flag.

6. Interview Flow & Pivots#

6.1 Typical 45-Minute Shape#

PhaseTimeGoal
Framing, intents, ratios0–4 min250:1, loss tolerance classes, commit to rides
Entities and API4–6 minVersioned trip commands, idempotency, driver socket
Architecture6–11 minThree planes, city sharding
Dispatch and double assignment11–19 minETA, lease, batch, conditional commit
Trip state machine and payments19–28 minStates, outbox, saga, risk policy
Location and surge28–37 minIn-memory, reconnect storms, control loop
Multi-region / delivery extension37–42 minCity home region; prep-time dispatch
Wrap-up42–45 minMetrics, evolution, skipped scope

6.2 How Interviewers Pivot — And What They're Testing#

PivotWhat They're TestingStrong Response
"Now add UberPool / shared rides"Assignment as routing; state machine with multiple riders"A vehicle trip holds N rider legs; insertion cost into the current route is the dispatch score; each leg has its own state machine."
"Now it's food delivery"Intent shift"Dispatch time = predicted ready time − courier ETA; order batching; merchant is a third state machine."
"Show the driver's car moving smoothly on the rider's map"Real-time push"Push via the rider's socket every 1–2 s during approach, interpolate client-side." See Real-Time Updates.
"A rider says the driver took a longer route"Location history, fare integrity"Map-matched route from history; upfront pricing limits exposure."
"Launch in a new country"Residency, payments rails, regulation"New home region if required; local payment methods (cash!) change the saga."
"The ETA service is down"Degraded mode"Haversine × per-city speed factor; flag ETAs as approximate; batching off."

6.3 What to Deliberately Skip#

TopicWhy SkipOne-Liner If Asked
Routing/ETA algorithm internalsSeparate system"Contraction hierarchies over a road graph with live traffic; I'll treat ETA as a service."
Map tile renderingNot relevant"Client SDK, CDN."
Driver onboarding, background checksBusiness workflow"Separate workflow system."
RatingsSimple aggregate"Async rating events, rolling average."
Fraud modelsSeparate team"Risk scores feed the pre-auth policy."

6.4 Follow-Up Questions to Expect#

  1. "Two riders request within 100 ms and the best driver is the same. What happens?"
  2. "A driver accepts, then their phone dies. What does the rider see, and when?"
  3. "How many writes per second hit your system, and which are durable?"
  4. "The card capture fails. Is the driver paid?"
  5. "Surge in one hex is 3× and the next hex is 1×. Is that a problem?"
  6. "A region dies. What happens to trips in progress?"
  7. "How would you change dispatch for food delivery?"

7. Active Drills#

Drill 1: The Opening#

Prompt: "Design Uber."

Staff Answer

"Three intents hide here — ride hailing, delivery, scheduled logistics. I'll design ride hailing and show what changes for delivery.

Two flows with very different shapes: ~1M online drivers pinging every 4 s is ~250K location writes/s; trips are ~25M/day, ~1K requests/s at peak. Location is superseded every 4 s, so losing it is free. Trip transitions move people and money, so losing them is never acceptable.

That gives me three planes: an in-memory location plane sharded by city, a per-city dispatch plane that holds one offer lease per driver, and a durable trip plane — a state machine with an outbox — that feeds payments, surge and receipts asynchronously. Payment is a saga that never blocks the ride."

Why this is L6:

  • Classifies data by loss tolerance before choosing storage
  • Commits to an intent and previews the delivery delta
  • Payment decoupled from the ride from the first minute

What L7 adds:

  • Names location, ETA and payments as shared platforms across rides and delivery, with product-owned dispatch policy
  • Notes that the ETA model is the most expensive shared asset and divergent copies would contradict each other to customers

Drill 2: Core Mechanic — Driver Accepts, Phone Dies#

Prompt: "The driver taps Accept and their phone dies before the response arrives. What happens?"

Staff Answer

"Two cases. If the accept never reached us, the lease expires at 15 s and the trip goes back to the next candidate — the rider sees 'finding your driver' a few seconds longer. If the accept reached us, the trip is DRIVER_ASSIGNED durably and the rider sees the driver. Now the driver is silent: no pings. Liveness rule: if an assigned driver's location is older than ~60 s and they haven't moved toward pickup, we notify the rider, and at ~2 min we move the trip back to MATCHING with a driver_unresponsive reason and release the driver to offline. When the phone comes back, the driver app fetches its current state by version and sees the trip was reassigned. The rider isn't charged a cancellation fee; the driver's acceptance-rate metric isn't penalized for a device failure if we can detect it."

Why this is L6:

  • Distinguishes 'accept lost' vs 'accept committed'
  • Defines liveness timeouts with numbers and a reassignment transition
  • Considers fairness to both parties

What L7 adds:

  • Makes driver-penalty rules a policy with a driver-advocacy owner, not a hard-coded metric
  • Tracks unresponsive-driver reassignment rate per device model and OS version as a platform-quality signal

Drill 3: Make It Concrete — Location Ingestion Sizing#

Prompt: "Size location ingestion and tell me where every byte goes."

Staff Answer

"1M online drivers at peak; adaptive intervals: 40% idle at 15 s, 45% available at 4 s, 15% on trip at 2 s. That's 1M × (0.4/15 + 0.45/4 + 0.15/2) ≈ 1M × (0.027 + 0.1125 + 0.075) ≈ 215K pings/s. At ~150 bytes each, ~32 MB/s, ~2.8 TB/day.

Where it goes: (1) connection gateway → the city's dispatch shard in-memory index (latest only, ~200 bytes × 1M = 200 MB total — trivial); (2) Kafka topic partitioned by city, 3-day retention for replay (~8.4 TB); (3) a consumer compacts to 30-second resolution for ETA training and disputes into object storage (~10× smaller, ~280 GB/day); (4) on-trip pings at full resolution attached to the trip record for fare integrity, 2-year retention.

I'd set retention with Privacy: full resolution only for trips; idle-driver history downsampled and deleted after ~30 days."

Why this is L6:

  • Numbers with adaptive rates
  • Every sink has a purpose and a retention
  • Privacy as a design input

What L7 adds:

  • Prices storage and Kafka ($/TB) and decides retention by business value per byte
  • Sets a company-wide location data policy across products

Drill 4: Dependency Down — ETA Service Outage#

Prompt: "The ETA service is returning errors for one city."

Staff Answer

"Dispatch falls back to haversine distance × a per-city, per-hour speed factor (e.g., 18 km/h downtown at rush hour) — precomputed and cached in the dispatcher. Batching is disabled in that city because the optimizer on bad ETAs does worse than greedy. Rider-facing ETAs are shown as ranges. Upfront pricing uses the last good route estimate for the O-D pair or falls back to a distance-based fare with a conservative cap.

Alert eta.fallback_ratio{city} > 10% for 5 min → page Routing. The trip plane is untouched."

Why this is L6:

  • Concrete fallback with a precomputed factor
  • Knows to disable batching when inputs are degraded
  • Protects pricing from bad estimates

What L7 adds:

  • Error budget for ETA shared by rides and delivery; delivery is more sensitive and may need priority

Drill 5: Hot Key — Airport Queue and Stadium Exit#

Prompt: "500 drivers wait at an airport lot; 3,000 riders request at once after a concert. What's hot?"

Staff Answer

"Airport: dispatch is FIFO by queue position (airport policy), not ETA. The queue is one hot key per airport — a sorted set of driver IDs by entry time on a single shard, ~1–5 ops/s per driver arrival and dispatch, fine. The hot part is geofence verification: drivers must be inside the lot to hold position.

Stadium: 3,000 requests in a few hexes means the dispatch shard for that zone and the lease store are hot. Mitigations: pre-planned event mode (schedule known from ticketing data) with extra dispatcher capacity for that zone, pickup zones that spread riders across 4–6 locations, request admission with an honest 'high demand — ETA 12 min' rather than overload, and surge to pull supply before the doors open."

Why this is L6:

  • Different hot keys, different fixes
  • Uses product (pickup zones, honest ETA) as load control
  • Pre-planned capacity for scheduled events

What L7 adds:

  • Partnerships with venues and airports as part of capacity planning; event calendars feed capacity automatically

Drill 6: Multi-Tenant — Rides and Delivery on One Platform#

Prompt: "Should rides and delivery share dispatch?"

Staff Answer

"Share the primitives, not the policy. Shared: location plane, ETA, lease store, the fulfillment state machine framework, payments. Separate: the dispatch objective (rides: pickup ETA now; delivery: ready-time-aware, batching), the solver cadence, and the state machine definitions.

The interesting shared case is a driver who does both. Then supply is shared and the platforms compete for the same driver — the lease store must be global per driver across products, so a driver can't hold a ride offer and a delivery offer at once. Priority between products for a shared driver is a marketplace policy decision, owned above both product teams."

Why this is L6:

  • Platform/policy split
  • Catches the shared-supply lease problem
  • Escalates the priority decision to the right owner

What L7 adds:

  • Designs the org: a fulfillment platform team providing primitives, product dispatch teams owning objectives, and a marketplace council arbitrating shared supply

Drill 7: Build vs Buy — Trip State Store#

Prompt: "Build your own trip store on sharded MySQL, or buy something like Spanner?"

Staff Answer

"The trip store needs per-trip linearizability, multi-row transactions (trip + driver state), cross-zone synchronous replication and fast failover. Sharded MySQL/Postgres by city gives the first two if trip and driver share a shard; replication and failover are ours to build and operate. A managed globally consistent store gives all four at a higher unit price.

Decision rule: if we have fewer than ~3 engineers who can own sharding, failover and resharding for years, buy. Uber publicly moved its fulfillment platform onto Spanner, which is a strong signal of where the cost curve lands at very large scale. I'd start on sharded Postgres by city with an abstraction over the store and revisit when cross-city transactions (e.g., shared drivers across cities) become common."

Why this is L6:

  • Requirements first, then options
  • Staffing as a decision input
  • Keeps an abstraction to preserve the option

What L7 adds:

  • Vendor lock-in and exit cost analysis; negotiates committed-use pricing; plans the migration path if needed

Drill 8: Policy Change Without Outage — New Surge Curve#

Prompt: "Pricing wants a new surge curve launched Friday."

Staff Answer

"Shadow first: compute both curves for 2 weeks and compare multipliers, volatility and projected conversion. Then city-level A/B (switchback experiments by time slot, since riders in one city interact through shared supply — user-level A/B is contaminated). Guardrails: completion rate, rider wait p90, surge.multiplier_volatility, driver earnings per hour. Caps and emergency override unchanged. Rollback is a versioned config snapshot, effective at the next 60 s cycle. Legal reviews the curve if it changes maximum multipliers."

Why this is L6:

  • Switchback experiments because of marketplace interference
  • Guardrails on both sides of the market
  • Config rollback within one cycle

What L7 adds:

  • Establishes a pricing-change review with Legal and Policy and a public-facing explanation where regulators require it

Drill 9: Cost#

Prompt: "Infra costs per trip rose 40%. Where do you look?"

Staff Answer

"Per-trip cost is mostly the location plane and ETA. Check: (1) ping interval regressions — a client release that dropped adaptive intervals doubles ingestion; (2) ETA calls per dispatch — batching with 200 candidates per request is 10K lookups per batch, so a candidate-radius change multiplies cost; (3) Kafka retention on location history grew; (4) surge recompute resolution changed from res 7 to res 8 (7× more hexes). I'd publish cost per trip by plane weekly with owner attribution."

Why this is L6:

  • Knows the cost drivers and their multipliers
  • Attribution by plane

What L7 adds:

  • Unit economics tied to take rate; decides which cost reductions are worth quality loss with Finance

Drill 10: Multi-Region#

Prompt: "Make it survive a region outage."

Staff Answer

"Cities are the unit. Each city has a home region; the trip store for that city replicates synchronously across 3 zones in-region and asynchronously to a paired region (RPO ~1–5 s). Location and dispatch state are rebuildable — after failover, the connection gateway in the paired region receives pings and rebuilds the index within one interval.

Failover: promote the replica, flip the city's home in the routing table, clients reconnect. In-flight trips: clients hold state and version; they re-submit their last action idempotently. Transitions lost in the RPO window are reconciled from client state and location history. I would not run active-active writes for the same trip in two regions — a trip needs one owner. Game day: fail a mid-size city quarterly."

Why this is L6:

  • City as failure unit; single-owner trips
  • Rebuildable vs durable state distinguished
  • Client-assisted recovery with idempotency

What L7 adds:

  • Cell-based architecture across the org with blast-radius targets (no single failure affects > X% of trips)
  • Budgets the paired-region capacity cost explicitly

8. Deep Dive Scenarios#

Deep Dive 1: New Year's Eve — Assignment Latency Collapse#

Context: 00:02 local, one of your top-5 cities. Request-to-assignment p50 jumped from 12 s to 90 s; cancellations are 4× normal. The on-call escalates to you.

Questions to Surface First:

  • Is supply exhausted (no available drivers) or is dispatch slow (drivers available but not offered)?
  • What are lease store and dispatch shard latencies in the hot zones?
  • Is the batch solver timing out?
  • Is surge responding, and are drivers moving toward hot hexes?

Typical L5 Approach: Scales the dispatch service horizontally and increases the lease store's memory. Doesn't check whether the solver or the supply is the bottleneck.

Staff Approach: Splits the problem in 2 minutes: drivers_available{zone} vs dispatch.pending_requests{zone}. Finds drivers available but solver batches taking 8 s (candidate set exploded because requests widened search radius). Caps candidates per request to 30 by ETA, shrinks batch window to 1 s in the hot zones, and turns on pickup-zone steering. Surge is at cap; confirms caps are policy, not a bug.

Principal Approach: Asks why the city's top event of the year wasn't in an event calendar with pre-provisioned solver capacity and pre-tuned parameters. Makes "known peak events" a planning artifact owned jointly by Marketplace Ops and Dispatch, reviewed 2 weeks ahead, with a load test at 3× last year's peak.

Staff Approach — Full Reasoning
PhaseWhat to Do
Immediate (0–5 min)Supply vs dispatch split; cap candidate set; shorten batch window
TriageSolver time per batch, ETA lookups per batch, lease latency
Quick fixGreedy fallback in zones where solver p99 > 2 s
GuardrailsWatch double-offer count = 0 and cancellation rate
Post-mortemSolver time budget enforced automatically; event calendar

Metrics to Watch: dispatch.assign_latency_p50{city}, dispatch.solver_time_p99{zone}, dispatch.candidates_per_request, drivers_available{zone}, trip.cancel_rate

Organizational Follow-up: Solver time budget with automatic greedy fallback; event calendar owned by Marketplace Ops.

Ownership Question: "Who decides to switch a city from batched to greedy during an incident?" Staff answer: The dispatch on-call, pre-authorized by runbook when solver p99 > 2 s for 2 minutes. It's automatic next time.

Key Takeaway: "When a solver's inputs grow, its runtime grows faster. Bound the inputs before you scale the fleet."

What clears the Staff bar:

  • Separates supply shortage from dispatch slowness
  • Bounds solver inputs
  • Turns manual switch into an automatic guardrail

Deep Dive 2: The Silent Failure — Drivers Unpaid for Completed Trips#

Context: Driver support tickets show ~0.3% of completed trips missing from weekly earnings. No alerts fired.

Questions to Surface First:

  • Are the trips COMPLETED in the trip store?
  • Did the trip_completed events reach the earnings consumer?
  • Is there an outbox relay gap or a consumer skipping on error?
  • Is it correlated with a region, app version, or payment status?

Typical L5 Approach: Finds a consumer bug that dropped events on deserialization errors and backfills the missing earnings.

Staff Approach: Finds the root: the earnings consumer caught schema errors after a trip event schema change (new optional field with a non-default type), logged them, and committed offsets — silently dropping events. Backfills from the outbox table (the source of truth). Adds a DLQ with an alert on depth > 0, and a daily reconciliation: COMPLETED trips vs earnings lines per driver per day; mismatch > 0 pages.

Principal Approach: Treats the trip event as a versioned public contract. Establishes a schema registry with compatibility enforcement in CI for all 20+ consumers, and a rule that money-moving consumers never skip — they stop and page. Makes the earnings reconciliation report visible to Driver Operations leadership.

Staff Approach — Full Reasoning
PhaseWhat to Do
ImmediateConfirm scope via outbox vs earnings diff
TriageIdentify the schema change and the skip-on-error path
Quick fixBackfill from outbox; notify affected drivers with amounts
GuardrailsDLQ + alert; money consumers halt on error
Post-mortemSchema compatibility checks; daily reconciliation

Metrics to Watch: earnings.reconcile_mismatch_total, consumer.dlq_depth{consumer}, consumer.deserialize_errors_total

Organizational Follow-up: Schema registry owned by Trip platform; consumers register and are notified of changes.

Ownership Question: "Who owns making the drivers whole?" Staff answer: Driver Earnings team, same week, with an explanation — and the fix is funded before the post-mortem closes.

Key Takeaway: "A consumer that moves money must never skip an event. It stops, and someone gets paged."

What clears the Staff bar:

  • Uses the outbox as a source of truth for recovery
  • Adds reconciliation, not just a bug fix
  • Classifies consumers by criticality

Deep Dive 3: Large-Customer Onboarding — Enterprise Delivery Partner#

Context: A national grocery chain wants to route 200K orders/day through your delivery platform, with scheduled windows and bulk orders (courier capacity limits matter).

Questions to Surface First:

  • What fraction is on-demand vs scheduled windows?
  • Vehicle capacity constraints (a 40-item order doesn't fit on a bike)?
  • SLA commitments and penalties?
  • Integration: API volume, order bursts at window open?

Typical L5 Approach: Adds a bulk-order API and relies on existing on-demand dispatch.

Staff Approach: Recognizes that scheduled windows are Intent 3 — a batch routing problem. Runs a VRP planner per window 60–90 min ahead, then hands planned routes to the real-time layer for execution and exception handling. Adds vehicle capacity as a dispatch constraint. Rate-limits the partner's order API with a per-tenant quota and burst allowance. Negotiates SLAs based on measured percentiles, not averages.

Principal Approach: Evaluates whether this partner justifies a scheduled-logistics platform the company doesn't have yet, prices it (team of ~8 for a year), and decides with the business whether to build, buy a routing vendor, or decline scheduled windows in the contract.

Staff Approach — Full Reasoning
PhaseWhat to Do
ScopingSplit on-demand vs scheduled volume
DesignVRP planner per window; capacity constraints; tenant quotas
PilotOne metro, 5% of partner volume, shadow planning
Ramp25% → 100% with SLA dashboards
Steady statePer-tenant SLOs, penalties tracked

Metrics to Watch: delivery.window_hit_rate{tenant}, planner.infeasible_routes, api.tenant_throttled_total

Organizational Follow-up: Enterprise onboarding checklist; account team sees SLA dashboard.

Ownership Question: "Who signs the SLA?" Staff answer: Business owns the contract; engineering signs the achievable percentile from pilot data. Nobody signs a number we haven't measured.

Key Takeaway: "A big customer can quietly change your intent. Recognize when on-demand dispatch is the wrong tool."

What clears the Staff bar:

  • Detects the intent shift
  • Tenant isolation
  • SLA grounded in measurement

Deep Dive 4: Post-Mortem — Riders Double-Charged After Failover#

Context: After a regional failover, ~12K riders were charged twice. Press is asking.

Questions to Surface First:

  • Were two captures issued, or one capture plus a new auth converted?
  • Were idempotency keys used, and were they scoped per processor account?
  • Did the payment saga state replicate with the trip state?

Typical L5 Approach: Refunds duplicates and adds a dedupe check before capture.

Staff Approach: Finds that the saga state was in a region-local store not replicated to the paired region; after failover the saga restarted for trips in COMPLETED and generated new idempotency keys with a random component. Fix: idempotency keys are deterministic (trip_id:capture:v1) and processor-side keys dedupe regardless of region; saga state replicates with the trip store; a pre-capture check reads the processor's charge list for the trip reference.

Principal Approach: Establishes an org-wide rule: any retry-capable money operation uses deterministic idempotency keys derived from business identifiers, audited in code review; failover game days include payments. Coordinates rider communication and refunds with Comms, and reports to the finance controller.

Staff Approach — Full Reasoning
PhaseWhat to Do
ImmediateStop saga replays; identify duplicates via processor reference
TriageKey generation, state replication gap
Quick fixAutomated refunds within 24 h with notification
GuardrailsDeterministic keys; pre-capture lookup
Post-mortemPayments included in failover game days

Metrics to Watch: payment.duplicate_capture_total, saga.restarted_total, payment.refund_auto_total

Organizational Follow-up: Payments failover runbook; quarterly game day.

Ownership Question: "Who decided the saga state didn't need replication?" Staff answer: Nobody decided — it was never classified. That's the fix: every store gets a data classification that determines replication.

Key Takeaway: "Idempotency keys must be derived from the business event, never generated at retry time."

What clears the Staff bar:

  • Pinpoints key generation as root cause
  • Replicates money state with trip state
  • Adds classification to prevent recurrence

Deep Dive 5: Multi-Region Expansion — Launching Southeast Asia#

Context: Launching in 6 cities across 3 countries with different payment rails (cash common), residency rules, and motorbike taxis.

Questions to Surface First:

  • Residency: must trip/location data stay in-country?
  • Cash payments: how does the saga change?
  • Vehicle types: motorbikes change ETA and pickup behavior.
  • Network: 3G fallback, high packet loss?

Typical L5 Approach: Deploys the existing stack in a Singapore region.

Staff Approach: Region cell in-region; per-country residency where required. Cash changes the payment saga: no pre-auth; completion records cash collected; the company's commission becomes a driver debt settled from future card trips or top-ups — a new state machine. Motorbike ETA model and vehicle class. Client: lower ping frequency on poor networks, compact binary payloads.

Principal Approach: Decides build-vs-partner per market (local wallets, maps), and sets a region-launch template. Assesses regulatory posture on surge per country before launch.

Staff Approach — Full Reasoning
PhaseWhat to Do
DesignRegion cell; residency map; cash saga; vehicle classes
Pre-launchETA model trained on local data; payment partners integrated
LaunchConservative surge caps; staffing on-call locally
Steady statePer-country SLOs; driver debt reconciliation

Metrics to Watch: driver.debt_outstanding, eta.error_p90{vehicle_class}, client.ping_loss_rate

Organizational Follow-up: Country launch checklist; local ops team ownership.

Ownership Question: "Who absorbs uncollected driver debt?" Staff answer: Finance sets the write-off policy; Risk sets debt limits before a driver is paused.

Key Takeaway: "A new payment rail isn't a config change — it's a new state machine."

What clears the Staff bar:

  • Payment-rail-driven design changes
  • Vehicle-specific models
  • Regulatory review before launch

9. Level Expectations Summary#

After studying this case study, you should be able to:

  • Classify ride-hailing data by loss tolerance and design three planes accordingly
  • Size location ingestion with adaptive ping rates
  • Prevent double assignment with leases plus a conditional commit
  • Draw the trip state machine including cancellations, no-shows and payment settlement
  • Design the payment handoff as a saga with deterministic idempotency keys
  • Explain surge as a smoothed, capped control loop with a legal owner
  • Explain how delivery changes dispatch timing
  • At L7: price the planes, draw platform vs product ownership, and plan the 3-year evolution

The Bar for This Question#

Mid-level (L4): Rider/driver/trip tables, nearest-driver query, charge at end. Works for one city of 100 drivers.

Senior (L5): Adds geohash/H3, Redis for location, Kafka, sharding. Knows about idempotency. Keeps payment synchronous, uses a status column, and misses double assignment or solves it with a global lock.

Staff+ (L6): Separates planes by loss tolerance, prevents double assignment structurally, models the trip as a state machine with outbox and payment saga, treats surge as a controller with policy owners, and uses the city as the unit of sharding and failure. Every failure has a metric and an owner. The interviewer should learn something from the answer.


10. Staff Insiders: Controversial Opinions#

10.1 "Don't Replicate Driver Location"#

EvidenceImplication
Pings arrive every ~4 sAny lost state is rebuilt in one interval
Replicating 250K writes/s costs real moneyPaying for durability the protocol already gives
Dispatch filters stale drivers anywayFreshness matters more than durability

The Staff position: In-memory latest location; history async for analytics.

Why this matters in interviews: It shows you reason from the protocol, not from habit.

10.2 "Batched Matching Is Overrated Outside Dense Cores"#

EvidenceImplication
Batching gains come from contention between nearby requestsSparse areas gain ~nothing
Every rider pays the batch windowLatency tax everywhere
Solvers add a hot spot and a failure modeOperational cost

The Staff position: Adaptive: batch where density justifies, greedy elsewhere.

Why this matters in interviews: Candidates who say "Hungarian algorithm" confidently rarely say where not to use it.

EvidenceImplication
Surge during emergencies has drawn regulatory action and public commitments to cap itLegal constraints shape the algorithm
Riders compare prices across neighboring blocksSpatial fairness is a trust issue

The Staff position: Caps, overrides and audit trails are first-class requirements.

Why this matters in interviews: Naming the non-engineering owner is a Staff-level signal.

10.4 "The Trip State Machine Deserves the Most Expensive Database You Have"#

EvidenceImplication
Trip writes are ~1% of total write volumeCost of a premium store is small in absolute terms
Trip inconsistencies cost support, refunds and trustCorrectness pays for itself
Uber publicly moved fulfillment to a strongly consistent storeIndustry signal

The Staff position: Cheap stores for location; strongly consistent store for trips.

Why this matters in interviews: Spending asymmetrically is the Staff move.

10.5 "Driver Earnings Must Never Depend on Rider Payment Success"#

EvidenceImplication
Drivers can't control rider cardsCoupling is unfair and drives churn
Collection risk is diversifiable for the company, not for a driverBusiness should absorb it

The Staff position: Credit at completion; company owns collection.

Why this matters in interviews: It shows you name who pays.


11. The Principal Lens (L7)#

Why L7 Sees This Problem Differently#

A Staff engineer designs a ride-hailing system. A Principal engineer sees a fulfillment company: rides, food, grocery, parcels and freight all assign mobile supply to located demand, track it, price it and pay it. The expensive, slow-to-build assets — the location plane, the road graph and ETA models, the payment and payout rails, the trip/order state machine framework — are shared, and every product team will try to fork them to move faster. The L7 question is how to keep one ETA, one location truth and one money path while letting products compete on dispatch policy and UX — and which decisions (city-as-cell, trip store technology, event contracts) will lock the company in for five years.

The Org-Level Fault Line#

One fulfillment platform vs per-vertical stacks.

OptionWhat WorksWhat BreaksWho Pays
Per-vertical stacks (rides, eats, grocery each build their own)Speed for each vertical early onETAs disagree between apps; drivers doing both get double offers; 3× location infra; three payment integrationsDrivers, Finance, customers comparing ETAs
Monolithic shared platformOne truthEvery vertical waits on one team; delivery-specific needs (prep time, batching) get starvedProduct velocity
Shared primitives + vertical policy (L7 default)Location, ETA, leases, state-machine framework, payments as platforms; dispatch objectives and state definitions per verticalContract design and versioning overhead; platform fundingPlatform headcount (~30–50 across teams at scale)

🧭 Principal Move: "The platform owns anything where two versions of the truth would embarrass us in front of a customer — ETA, location, money. Verticals own anything that is their competitive difference — dispatch objective, batching, UX. The lease store is global per driver because the driver is a shared resource."

Cost Model#

Assumptions: cloud list prices order-of-magnitude; adaptive pings averaging ~5 s; ~150-byte pings; strongly consistent trip store; loaded engineer ~$250K/yr.

ScaleInfra $/monthDominant CostHeadcount (eng)On-Call Load
1 city, ~5K online drivers, ~50K trips/day~$20–40KSingle Postgres/PostGIS, app servers, maps API fees6–10 in one team1 rotation, a few pages/week
50 cities, ~100K online drivers, ~2M trips/day~$400K–1META/routing compute, location ingestion, maps licensing60–120 across ~8 teamsPer-plane rotations
Global, ~1M online drivers, ~25M trips/day~$5–15META compute (~30%), location + Kafka (~25%), trip store (~15%), data/ML500+ across platforms and verticalsPer-plane + per-region; follow-the-sun

The line that moves the business case: ETA lookups per dispatch. A candidate-set or batch-size change can move infra cost by double-digit percent — ETA request volume should be a budgeted, reviewed quantity.

The 3-Year Evolution Path#

Diagram: The 3-Year Evolution Path

One-Way Doors vs Two-Way Doors#

DecisionReversibilityCost to ReverseWhy
City/cell as the shard and failure unitOne-wayRe-architecture of every planeAll data placement follows it
Trip event schema published to 20+ consumersOne-way-ishMulti-quarter deprecationConsumers depend on fields forever
Trip store technologyOne-way-ishLive migration of the most critical data, 6–12 monthsKeep an abstraction layer
Driver earnings decoupled from rider paymentOne-way (trust)Reversing is a driver-relations crisisCommitments to supply are sticky
Ping interval / adaptive policyTwo-wayClient releaseTune freely
Batch vs greedy per zoneTwo-wayConfigExperiment freely
Surge curveTwo-way technically, one-way reputationallyHeadlinesLegal review

The Standard I'd Write#

RFC: Fulfillment State & Money-Movement Standard (v1)

Scope: Every service that creates or transitions a trip, order or delivery, or that moves money as a result.

Mandatory requirements:

  • Fulfillment entities MUST be modeled as explicit state machines in the shared framework; transitions MUST be versioned and validated server-side.
  • State changes MUST emit events via a transactional outbox; direct dual writes to a database and a broker are prohibited.
  • Money-moving calls MUST use deterministic idempotency keys derived from (entity_id, step, version).
  • Money-moving consumers MUST NOT skip events on error; they halt and page.
  • A supply unit (driver/courier) MUST hold at most one active offer across all verticals, enforced by the global lease service.
  • Location history SHOULD be downsampled outside active trips and deleted after the retention set by Privacy.

Exceptions: Filed with Fulfillment Platform and Payments; time-boxed to one quarter.

Success metrics: 0 double assignments per quarter; 0 duplicate captures; earnings reconciliation mismatch = 0 for 30 days; 100% of verticals on the shared state framework within 4 quarters.

What I'd Tell the VP#

"Our riders and drivers only notice three things: how fast they're matched, whether the price makes sense, and whether the money is right. Today each of our apps builds its own version of those systems, which is why ETAs sometimes disagree and why a failover double-charged customers last quarter. I'm proposing one fulfillment platform that owns location, ETAs, trip state and money movement, with each product team free to design its own dispatch rules on top. It costs roughly 30 engineers we mostly already have, reorganized. The payoff is fewer money incidents, faster launches of new verticals, and one set of numbers our drivers can trust."

Principal Interview Signals#

SignalWhat It Sounds Like
Portfolio view"Rides, eats and grocery share supply. The lease is per driver, not per product."
Prices the plane"ETA lookups per dispatch are our biggest cost lever — I'd budget them."
One-way doors"City-as-cell is the decision everything else inherits. I'd make it deliberately."
Writes the standard"Outbox mandatory, deterministic idempotency keys, money consumers never skip."
Supply-side governance"Driver earnings are decoupled from rider payment — that's a commitment, not a config."

Staff answers that L7 interviewers find insufficient:

  • "We'll use an outbox." — Correct, but doesn't make it the org standard or govern the event schema consumed by 20 teams.
  • "Surge has caps." — Correct, but doesn't say who owns the caps across jurisdictions or how emergency commitments are enforced.
  • "Delivery can reuse dispatch." — Correct, but doesn't address shared supply, cross-vertical priority, or who arbitrates.

Appendices

Appendix A: Mechanics in Depth#

A.1 Candidate Generation#

cells      = h3.k_ring(h3.cell(pickup, res=9), k=ring_for(radius))   # k=5 ≈ 1.5 km
candidates = [d for c in cells for d in index[c]
              if d.status == 'available' and now - d.ts < 20s
              and d.vehicle_class in product.classes]
candidates = top_n_by(haversine, candidates, 100)      # cheap prefilter
etas       = eta_service.matrix(candidates, pickup)     # one batched call
ranked     = sort_by(eta + deadhead_penalty + fairness_adj, candidates)[:30]

A.2 Batched Assignment#

For a zone every 2–3 s: build cost matrix C[r][d] = eta(d, r) + penalties; add dummy columns with a high "wait for next batch" cost; solve min-cost assignment (Hungarian O(n³) is fine for n ≤ ~200; beyond, partition by zone). Requests unassigned roll into the next batch; after ~3 batches, fall back to greedy with a widened radius.

A.3 Surge Smoothing#

Temporal EWMA with α ≈ 0.3–0.5 over 60–120 s cycles; spatial averaging across the k=1 ring; hard clamp by policy cap; emergency override sets cap = 1.0 for a polygon in < 60 s.

Appendix B: Keys and Data Model#

EntityKeyStoreNotes
Driver latest locationdriver_id in city shard memory, indexed by H3 res 9In-processRebuilt from pings
Offer leaseoffer:{driver_id}Redis per city (global per driver across verticals at L7)SET NX PX 15000
Triptrip_id (embeds city)Strongly consistent store sharded by cityVersioned
Driver statedriver_id co-located with city shardSame store as tripsSingle-shard transactions
Outbox(trip_id, version)Same transaction as tripRelay to Kafka
Payment intenttrip_idPayments ledgerDeterministic keys
Surge snapshot(city, version) → hex multipliersCache + logQuotes embed version
Location history(city, date, driver_id)Object store via KafkaDownsampled

Appendix C: Coordination Mechanisms — Quick Comparison#

MechanismLatencyGuaranteesFailure BehaviorUse
Redis SET NX PX lease< 1 msOne offer per driver while Redis healthyLease lost on failover → possible double offer, caught by commitOffers
Conditional transaction on trip + driver rows5–20 msLinearizable per shardRejects on conflictAccept, transitions
Single-writer zone dispatcher< 1 msNo conflicts within zoneZone failover pauses offers ~secondsVery high density
Global 2PC50+ msCross-shardBlocks on coordinator failureAvoid
Workflow engine timerssecondsDurable timeoutsEngine is a dependencyNo-show, matching timeout

Appendix D: API Contract and Client Behavior#

  • Trip creation requires idempotency_key; duplicates return the existing trip.
  • All trip commands carry version; stale versions return 409 with current state.
  • Driver socket: server sends offers with expires_in; client must respond within it; late accepts return offer_expired.
  • Reconnect: jittered exponential backoff (base 1 s, cap 30 s); on reconnect send only the latest location; fetch current trip state by version.
  • Rider tracking: server pushes driver position every 1–2 s during approach; client interpolates.
  • Quotes: valid until expires_at; trip creation with an expired quote returns a fresh quote for confirmation.

Appendix E: Observability#

E.1 Core Metrics#

MetricWhy
dispatch.assign_latency_p50/p99{city}Core promise
dispatch.double_offer_total, trip.assign_conflict_totalAssignment invariant
location.staleness_p99_s{city}Location plane health
trip.transition_errors_total{from,to}Illegal or failed transitions
outbox.relay_lag_sEvent pipeline health
payment.capture_failure_rate, payment.duplicate_capture_totalMoney correctness
earnings.reconcile_mismatch_totalSilent money failure
surge.multiplier_volatility{hex}Controller stability

E.2 Critical Alerts#

AlertThresholdSeverity
Double offer> 0 in 5 minPage dispatch
Assignment p50> 3× baseline for 5 min in a top-20 cityPage dispatch
Location stalenessp99 > 15 s for 3 minPage edge
Outbox lag> 60 sPage trip platform
Duplicate capture> 0Page payments (SEV2)
Earnings mismatch> 0 dailyPage earnings

E.3 Control Plane vs Data Plane#

Data plane: pings, offers, transitions, captures. Control plane: surge curves and caps, dispatch parameters (batch window, candidate caps), city-to-region mapping, ping policies. Control-plane changes are versioned, canaried by city, and reversible within one cycle.

Appendix F: Scale Evolution#

ScaleWhat WorksWhat You Add
< 10K online driversOne Postgres + PostGIS; greedy dispatch; row locksState machine + outbox
10K–100KRedis GEO per region; leases; Kafka; payment sagaAdaptive pings, surge controller
100K–1MIn-memory location per city shard; city cells; paired regionsBatched dispatch in dense cores
1M+Fulfillment platform; global driver leases; region-in-a-boxCross-vertical marketplace policy

F.1 What You Don't Build on Day One#

  • Batched matching
  • In-process location sharding
  • Multi-region active failover
  • Custom routing engine (use a maps provider)
  • Delivery-specific dispatch

What you do build on day one: the explicit trip state machine with outbox, deterministic idempotency keys for money, and driver earnings decoupled from rider payment.

Appendix G: Multi-Tenancy, Fairness and Cost#

  • Driver fairness: track trip distribution and earnings per online hour across drivers in a zone; dispatch adds a small fairness adjustment for drivers idle longest.
  • Rider fairness: monitor pickup ETA by neighborhood; dispatch radius policies should not systematically exclude lower-density areas.
  • Tenant isolation (enterprise delivery partners): per-tenant API quotas and burst allowances; per-tenant SLO dashboards.
  • Cost attribution: cost per trip by plane and by vertical, reported monthly; ETA lookups per dispatch budgeted.
  1. Loading the index…