System Design (High-Level Design) Module 25 – Case studies: location, mobility and logistics
Design ride hailing: trips, pricing, payments
Design the trip side of ride hailing like Uber or Ola: requirements, estimates and a first design for trips, fares, surge pricing and payments.
What you will learn
- Estimate trip changes, storage, payment calls and pricing cells for a national ride-hailing service
- Implement a trip as a state machine whose commands are idempotent and conditional on its version
- Compute surge multipliers from supply and demand per cell, smoothed so that they do not flap
- Design card payments as a saga that holds at booking, captures once and compensates on cancellation
- Record wallet, cash and pay-after-ride payments in a balanced ledger that receipts and payouts read
Before you start
What you’ll learn · Before you start
What you will learn
- Estimate trip changes, storage, payment calls and pricing cells for a national ride-hailing service
- Implement a trip as a state machine whose commands are idempotent and conditional on its version
- Compute surge multipliers from supply and demand per cell, smoothed so that they do not flap
- Design card payments as a saga that holds at booking, captures once and compensates on cancellation
- Record wallet, cash and pay-after-ride payments in a balanced ledger that receipts and payouts read
Before you start
On this page
Once a driver accepts, a ride becomes a trip: a record that changes state about 6 times in half an hour, carries a fare the rider agreed to, and ends with money moving between the rider, the driver and the platform. Across 8 million trips a day that is about 1,390 changes a second in the busiest hour, and none of them may happen twice.
The design rests on three ideas. The trip is a durable state machine: every change is a command with an idempotency key, applied only if it is legal and the trip is still at the version the sender saw. The fare is quoted and locked when the rider confirms, with a surge multiplier smoothed so that it does not flap. And payment is a saga: the money is held at booking, captured when the trip ends and released on cancellation.
Not affiliated
Based on public information about Uber and Ola. MySmartCoPilot is not affiliated with Uber and Ola, and Uber and Ola’s real architecture may differ from what this lesson describes.
About 1,390 trip changes a second, and none may happen twice
Ask these first, and note the answer you assume:
- Where does it start? When a driver accepts an offer, where matching ends.
- How big? 8 million trips a day, each changing state about 6 times; 60 % paid by card, the rest from a wallet, in cash or through a bank app after the ride.
- How are fares set? A quote before booking, from distance, time and a surge multiplier, honoured unless the trip changes materially.
- What goes wrong? Phones retry and drop off the network, riders and drivers cancel, payments fail or report late, and riders dispute fares.
- What must never happen? A rider charged twice, a driver unpaid for a completed trip, or a fare different from the quote without a stated reason.
So every state change must survive a retry or a lost reply without happening twice, a quote must still be honoured minutes later, and a failure of the payment gateway must never stop trips from ending.
| Quantity | Assumption | Result |
|---|---|---|
| Trip state changes, busiest hour | 8 million × 6 ÷ 86,400 s × 2.5 | about 1,390 a second |
| Trip records | 5 KB each, 3 copies, a year | about 44 TB a year |
| Route traces | a point every 4 s for 20 minutes, 16 bytes each | about 4.8 KB a trip, 38 GB a day |
| Payment gateway calls, busiest hour | 60 % of trips by card, a hold and a capture each | about 280 a second |
| Pricing cells per city | 600 km² at H3 resolution 8, 0.737 km² a cell | about 815 |
The cell size comes from H3’s table of average hexagon areas (H3, cell statistics). None of these numbers is large; what makes the trip side hard is correctness under retries, money that moves in several steps, and prices that change by the minute.
Exercise · Easy · Python
Size trip events, storage, payment calls and pricing cells
Write four functions in sizing.py for the estimate sheet of the trip side of ride hailing. A day has 86,400 seconds, a year 365 days, and 1 TB is 10¹² bytes.
trip_events_per_second(trips_per_day, events_per_trip, peak_factor): state changes a second in the busiest hour, when that hour runs atpeak_factortimes the day's average rate.trip_storage_tb_per_year(trips_per_day, bytes_per_trip, copies): the storage a year of trip records needs, kept incopiescopies.payment_calls_per_second(trips_per_day, card_share, calls_per_card_trip, peak_factor): calls to the payment gateway a second in the busiest hour, when a sharecard_shareof trips is paid by card.pricing_cells(city_km2, cell_km2): how many cells cover a city, rounded up, so that surge can be computed for each.
The sample tests accept each figure within 10 %, as an estimate should.
Starter code · sizing.py
import math
DAY_SECONDS = 86_400
def trip_events_per_second(trips_per_day, events_per_trip, peak_factor):
"""Trip state changes a second in the busiest hour."""
# Replace this line with your code.
return 0
def trip_storage_tb_per_year(trips_per_day, bytes_per_trip, copies):
"""TB a year of trip records, in `copies` copies."""
# Replace this line with your code.
return 0
def payment_calls_per_second(trips_per_day, card_share, calls_per_card_trip, peak_factor):
"""Payment gateway calls a second in the busiest hour."""
# Replace this line with your code.
return 0
def pricing_cells(city_km2, cell_km2):
"""Cells that cover the city, rounded up."""
# Replace this line with your code.
return 0 The sample tests · test_sizing.py
import math
from sizing import payment_calls_per_second, pricing_cells, trip_events_per_second, trip_storage_tb_per_year
def near(actual, expected):
"""Within 10%, as an estimate should be."""
return math.isclose(actual, expected, rel_tol=0.10)
def test_trip_events():
"""state changes spread over the day, times the peak factor"""
assert near(trip_events_per_second(8_000_000, 6, 2.5), 1_389)
assert near(trip_events_per_second(500_000, 8, 3), 139)
def test_trip_storage():
"""a year of records in every copy"""
assert near(trip_storage_tb_per_year(8_000_000, 5_000, 3), 43.8)
assert near(trip_storage_tb_per_year(500_000, 4_000, 2), 1.46)
def test_payment_calls():
"""only card trips call the gateway, twice each"""
assert near(payment_calls_per_second(8_000_000, 0.6, 2, 2.5), 278)
assert near(payment_calls_per_second(500_000, 0.3, 3, 3), 15.6)
def test_pricing_cells():
"""cells that cover the city"""
assert near(pricing_cells(600, 0.737), 815)
assert near(pricing_cells(600, 5.16), 117) A hint
Rates are trips_per_day * per_trip / 86_400 * peak_factor; the payment rate also multiplies by card_share. Storage multiplies trips, bytes, 365 and copies, then divides by 10**12. For cells, divide the areas and round up with math.ceil.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Checkpoint
Check the estimate
The first design: a trip store, a pricing service and a payment saga
The first complete design of trips, pricing and payments
Text description of the diagram
The diagram shows the parts of the trip side of ride hailing from top to bottom.
- The rider and driver apps call the API gateway.
- For a quote, the gateway calls the pricing service, which reads supply and demand counts per cell, refreshed each minute.
- Commands that change a trip go to the trip service, a state machine that writes to the trip store with conditional writes; the store keeps each trip's version and the idempotency keys it has seen.
- Every change also writes an outbox row, which is published to the trip event log.
- The payment saga reads the log and asks the payment gateway to hold, capture or cancel payments, recording every movement of money in the ledger.
- Receipts, driver payouts and disputes also read the trip event log.
Apps get a quote from the pricing service, which reads supply and demand counts per cell. Commands that change a trip go to the trip service, which writes them to the trip store with conditional writes and an outbox row for each change. The trip event log feeds the payment saga, which talks to the payment gateway and records every movement of money in the ledger, and the services that issue receipts, pay drivers and handle disputes. Follow one trip through it:
The drawing shows the whole scene; its 8 steps are listed below.
Every step as text
- A quote. The rider's app asks the pricing service for a fare. Pricing reads the counts of requests and drivers in the rider's cell and returns a quote.
- Booked. The rider confirms the quote, a driver accepts, and the trip begins. Every change from now on is a command to the trip service.
- A command. The driver's app sends a command, such as arrived. The trip service writes it to the trip store with a conditional write, and an outbox row with it.
- An event. The outbox row is published as an event in the trip event log, where everything after the trip reads it.
- A hold. The payment saga reads the booking and places a hold on the rider's card through the payment gateway.
- A capture. When the completion event arrives, the saga captures the fare through the gateway.
- The ledger. Every movement of money is recorded in the ledger, entry by entry.
- After the trip. Receipts, driver payouts and disputes are built from the same events, without touching the trip.
The questions this design has to answer:
- Trip states: commands that are retried, reordered or sent by two people at once each apply once or not at all.
- Fares: a quote is locked when the rider confirms and honoured unless the trip changes materially.
- Surge: supply and demand per cell set a multiplier that follows real changes without flapping every minute.
- Payments: a hold at booking, a capture at the end and a release on cancellation form a saga.
- Cash and paying later: a ledger keeps what each driver and rider owes exact.
- After the trip: receipts, payouts and disputes are built from the same events.
An answer is judged on the estimates, the trip’s states, fares and surge, payments by each method, what follows the trip, and failures.
Checkpoint
Check the first design
A trip applies each command once, at the version its sender saw Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A quote locked at booking is the fare unless the trip changes
- Smoothing keeps surge from changing 26 times an hour
- A card payment holds at booking and captures once at the end
- Wallets, cash and paying later meet in one ledger that sums to zero
- Receipts, payouts and disputes read the trip’s events, never the trip
- What gives way at 10× and 100×
- Failure drills: trips end even when payments fail
- Alternatives considered and rejected
- What each level is expected to cover
- A sample answer and a 45-minute self-mock
- Practise: the whole design, one question at a time
- Interview questions
- On a bad network a driver's phone sends "complete trip" three times, and one copy arrives ten minutes later, after the rider has already disputed the fare. Design the trip's state changes so that this can never charge twice or reopen a settled trip.
- During evening rain, the surge multiplier in one area jumps between 1.0 and 2.1 from minute to minute, and riders complain that the price changes every time they open the app. Design the surge computation so that prices follow real changes without flapping.
- Design payment for trips that can be paid by card, from a wallet or in cash, where the final fare is known only at the end and the rider may cancel at any point. Where are the points of no return, and what does each failure undo?
- Key takeaways
3 diagrams · 4 runnable examples (JavaScript, Python) · 3 interview answers · about 50 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Read a complete sample first: Design ride hailing like Uber or Ola: matching
References
- H3 documentation: Tables of cell statistics across resolutions (H3 project)
- Saga design pattern (Azure Architecture Center) (Microsoft)
- Transactional Outbox pattern (Azure Architecture Center) (Microsoft)
- Idempotent requests (API reference) (Stripe)
- Place a hold on a payment method (Stripe)
- Payments, payment states (Razorpay)
- About UPI (payment methods) (Razorpay)
- Apache Flink documentation: Windows (Apache Software Foundation)
- NIST/SEMATECH e-Handbook of Statistical Methods: Single Exponential Smoothing (NIST)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress