Problem RestatementProblem
OpenAI asked: you operate a ride-hailing platform. Design a system that sets surge multipliers (e.g., 1.5x) for each region in near real time. The pricing strategy must balance rider experience (not too expensive, rides available), driver supply (higher prices attract drivers to busy areas), and marketplace efficiency (most requests get matched quickly).
Why Surge Exists (simply)
When many people request rides in one area and few drivers are there, wait times explode. A higher price (1) reduces some demand (people wait or walk) and (2) attracts drivers to that area. The goal is to keep waiting times reasonable, not to maximize price.
ArchitectureArchitecture
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
RQ["Ride requests / app opens"] --> K[("Kafka - demand events")]
DL["Driver locations + status"] --> K2[("Kafka - supply events")]
K --> AGG["Stream aggregation per H3 cell, per minute"]
K2 --> AGG
AGG --> CALC["Surge calculator - every 1-2 min"]
FC["Short-term demand forecast"] --> CALC
CALC --> SM[("Surge map - cell to multiplier, version")]
SM --> QUOTE["Pricing / quote service"]
QUOTE --> APP["Rider app - price shown, locked for N min"]
SM --> DRV["Driver app - heat map"]The Calculation
- Zones: split the city into hexagonal cells (e.g., Uber's H3 at a neighborhood size), and group small cells to avoid noisy tiny areas.
- Measure per cell, per minute:
- Demand: ride requests plus app opens (people checking prices), including recently unfulfilled requests.
- Supply: available drivers in or near the cell (plus drivers about to finish trips nearby), and estimated pickup times.
ratio = demand / effective_supply, or better, the predicted ETA / probability a request goes unfulfilled.
- Multiplier: map the imbalance to a multiplier through a curve, e.g., 1.0 when balanced, rising gradually, and capped (e.g., max 3x, lower during emergencies by policy or law).
- Smoothing: combine with the previous value (e.g., exponential smoothing), and limit how fast it can change, so prices don't jump up and down every minute (oscillation). Spatially smooth with neighboring cells to avoid sharp price borders.
- Forecasting: add expected demand spikes (a concert ending, rain starting) to act slightly early.
Deep Dive — Showing a surge price a rider will acceptDeep dive
The multiplier moves with supply and demand. The rider needs a number they can trust before they commit.
Price at the moment of charging
Compute the multiplier when the trip is confirmed.
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
Q["Rider sees estimate: $18"] --> WAIT["They think about it for 40 s"]
WAIT --> CONF["Confirm"]
CONF --> RECALC["Multiplier recomputed - demand rose"]
RECALC --> CHARGE["Charged $26"]
CHARGE --> ANGRY["Different from the number they agreed to"]The rider is charged a price they never saw. It also creates a perverse incentive to hesitate or retry, and it is the kind of thing regulators and journalists write about.
Show the multiplier and refresh it
Display the current multiplier, recomputing as the rider looks at the screen.
Honest, and unusable: the price changes while they are deciding. Riders re-open the app hoping for a lower number, which adds load and produces no commitment — the estimate is information rather than an offer.
Lock a quote for a few minutes
%%{init: {"look":"handDrawn","handDrawnSeed":7,"theme":"base","fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","themeVariables":{"fontFamily":"Virgil, \"Segoe Print\", \"Comic Sans MS\", cursive","fontSize":"16px","primaryColor":"#fff4e6","primaryBorderColor":"#1e1e1e","primaryTextColor":"#1e1e1e","secondaryColor":"#e7f5ff","tertiaryColor":"#ebfbee","lineColor":"#1e1e1e","textColor":"#1e1e1e","mainBkg":"#fff4e6","nodeBorder":"#1e1e1e","clusterBkg":"#f8f9fa","edgeLabelBackground":"#ffffff","classText":"#1e1e1e"}}}%%
flowchart LR
MAP[("Surge map: cell -> multiplier, version, timestamp")] --> QS["Quote service - reads in milliseconds"]
QS --> QUOTE["Quote: price + quote_id, locked for a few minutes"]
QUOTE --> RIDER["Rider sees exactly what they will pay"]
RIDER --> BOOK["Books with the quote_id - honoured even if surge rose"]
QUOTE --> EXP["Expires - a fresh quote is issued"]
MAP --> DRV["Drivers see a heat map - where to reposition"]
GUARD["Caps, emergency freeze, no per-rider personalisation"] --> QS- A quote is an offer, not an estimate. It carries an id and an expiry, and it is honoured for its lifetime — so the rider commits to a number rather than to a formula.
- Reading the surge map is a lookup, because it is precomputed per cell and replicated. Quoting must be fast and must not depend on recomputing demand.
- Drivers get the map too, which is the mechanism by which surge is supposed to work: the multiplier exists to move supply, and it only does that if drivers can see where.
The guardrails are part of the design, not an afterthought: caps on the multiplier, an emergency freeze for disasters and severe weather, and no personalisation per rider — the multiplier depends on the place and time, never on who is asking. That last one is what keeps the mechanism defensible, and it is worth stating unprompted.
Evaluate the whole thing with experiments on completion rate and wait time, not on revenue per trip. Surge that raises revenue while riders give up is a failure the obvious metric will call a success.
Measuring Success (experiments)
- Metrics: request completion rate, pickup ETA, rider conversion (people who accept the price), driver earnings, and cancellations.
- A/B test curve shapes and smoothing parameters, by city or with switchback experiments (alternating time windows), since the marketplace effects spill across users.
Wrap-UpWrap-up
Split cities into H3 cells, stream demand (requests, app opens) and supply (available and soon-available drivers) into per-cell, per-minute aggregates, and every minute or two compute a multiplier from the imbalance (or predicted ETA) through a capped curve, smoothed over time and space and nudged by short-term forecasts. Publish a versioned surge map read by the quote service, lock quoted prices for a few minutes, show drivers the heat map, enforce caps and fairness rules, and tune it all through marketplace experiments.