InfoWok

Design a Ride-Sharing Service: Failures, Hot Cells, Surge and Payments (2026)

The working ride-sharing design is half the round. The other half is production: what breaks and what already handles it, a stadium crowd on one cell, two regions, surge pricing that stays stable, payments that charge once, private location data, and the SLOs and follow-ups interviewers ask.

SK
Sukhveer Kaur
Published October 24, 2026
11 min read
Open in ChatGPT Open in Claude
Ride-sharing follow-ups: a failure table for the location index, matcher and ride store, a stadium crowd on one hot cell, and a smoothed surge multiplierSoftware Architecture
RIDE SHARING FOLLOW-UPS
On this page +

A team ships the ride-sharing design from the previous lesson. It indexes by place, ranks by road and claims once. For a month it works.

Then a concert ends and ten thousand people open the app. The routing service times out. A card is charged twice after a retry. A reviewer asks who can see where a driver was last Tuesday. An interviewer, having heard the happy path, says the sentence that fills the last twenty minutes: “Now what breaks?”

A design is not finished until you can say how it fails, what the money does, and who can see the map.

Intermediate40 min
Before you start

Before You Start#

Remember from the previous lesson

Why is the Redis claim not enough to stop a double booking? Redis replicates asynchronously, so a failover can lose a claim. The accept is a conditional write on the durable ride record, and only one passes.

Key takeaways
  • Walk a failure table component by component: what dies, what riders see, and the answer already designed in. When the claim store or ride store is down, stop offering rather than risk a double booking.
  • A hot cell is a demand problem, not a matching-speed problem: batch, queue riders on the server so apps stop re-requesting, use fixed pickup points and give an honest wait.
  • Smooth the surge multiplier, cap it, and lock the price the rider was shown.
  • Authorize on request, capture once at completion, and key every payment call on the ride id, with your own ledger row behind it.
  • Location is personal data: share it only as far as the trip needs, keep trails for a limited time and log access.
After this lesson you can

Defend a working ride-sharing design in the second half of a system design round: name what fails and what already handles it, absorb a stadium crowd, place the service in regions, keep surge stable, charge a rider exactly once, protect location data, set SLOs and answer the usual follow-ups within a 45-minute plan.

Predict first. The routing service is down. Should the matcher stop offering rides, or fall back to something? Decide before you read the first rows of the table below.

What Breaks, and What Do You Watch?#

If this diesWhat riders seeAnswer
Location index (one region)Few or no cars shown; matches pauseDrivers re-report within one interval (4 s), so the index rebuilds in seconds; a standby process takes over
Routing serviceSlower or worse ETAsFall back to straight-line distance times a detour factor (judgement), mark the ETA as approximate; do not stop matching
Traffic feedETAs ignore live trafficUse stored typical speeds by hour; alert on feed age
Claim store (Redis)Offers cannot be made safelyPause offers, keep riders in requested; the ride record stays the truth
Ride storeCannot create or change ridesReturn an error so apps retry with the same request_id; never accept without a stored record
Matcher instanceOffers in flight stallAnother instance resumes from stored offered rides past expires_at
Gateway (held connections)Apps reconnectResume from the last event number; apps fetch state with GET
Push providerNudges are lateHeld connections and polling still show the state
Payment providerCapture failsRetry with the same idempotency key; the trip is not blocked

The rule behind the table (judgement): fail toward safety for the claim and the money, and toward a worse answer for the ETA. A rough ETA still gets a rider home; a duplicate claim or duplicate charge does not.

What Do You Watch?#

Watch these signals, each with a percentile or a rate, because averages hide a slow tail:

  • Time to match, from request to accepted offer, at the median and the 95th percentile.
  • Offer accept rate and claim conflict rate. A drop in accepts means drivers are being offered bad rides; a rise in conflicts means a hot cell.
  • Location staleness: the age of the oldest driver entry still counted as online.
  • Routing latency at the 99th percentile, and the age of the traffic feed.
  • Double bookings: a count that must be zero. Any non-zero value pages a person, whatever the rate.

An SLO turns these into promises. Assume (a made-up target) that 95% of requests are matched, or told “no cars” honestly, within 10 seconds, and location staleness at the 99th percentile stays under 10 seconds. Alert on how fast the budget is being used, not on every blip.

Cost and Rollout#

This section is design judgement, not measured fact.

  • Routing is likely the largest per-request cost, so cache ETAs between pairs of cells and call the matrix once per batch, not once per candidate.
  • Gateways scale with connections: at an assumed 50,000 per node, 500,000 drivers need 10 nodes, and 20 with room to fail over.
  • Rollout goes city by city behind a flag. Run the new matcher in shadow (decide, do not offer), compare time to match and pickup ETA with the old one, and keep a switch back to nearest-first.

What Happens When a Stadium Empties?#

Concept in use: Sharding and replication

Splitting by city does not help when the crowd is in one corner of one city. The load follows the event, not the shard map.

Assume 10,000 riders ask within five minutes, near about 1,000 drivers. That is 10,000 / 300 s ≈ 33 requests a second, so the matcher’s speed is not the problem. Every search reads the same cells, ranks the same drivers and tries to claim them, so most claims fail.

Worse, an unmatched rider taps again. If every waiting rider re-requests every 10 seconds (an assumed behaviour), 10,000 / 10 = 1,000 requests a second, about 30 times the first number.

  • Works: Do nothing special. Every request runs the normal search and tries to claim the top driver. Trade-off: Claims collide, riders retry, and the retries become the load. Drivers in the crowd are offered rides they cannot reach through the traffic.

  • Scales: Batch, queue on the server, widen. Match in short batches so the drivers nearest to the venue are shared sensibly. Keep the rider’s request open on the server in the requested state, so the app waits for a push instead of re-requesting, and the same request_id makes any retry harmless. Widen the search ring to bring in drivers from farther away, and apply client backoff with random jitter. Trade-off: A batch and a queue add a wait. A wide ring brings in drivers who need longer to arrive.

  • Survives: Pickup points, supply nudges and an honest wait. Move pickups to a few marked points so cars are not stuck in the crowd, and answer each rider quickly with a wait estimate based on queue length. Encourage drivers to head toward the area before the event ends (a product and pricing decision). Limit concurrent searches per cell, and drop to coarser results when the cell is saturated. Trade-off: Fixed points mean a walk. Nudging supply costs money, and admission control must still tell the rider the truth.

Where Does It Run?#

Concept in use: Sharding and replication

Each city is a shard with its own location index, claim store and ride store. A ride lives and dies in one city.

  • Works: One region, restore elsewhere. Everything runs in one region with backups elsewhere. Trade-off: A region outage stops every city in it until the restore ends.

  • Scales: Each city has a home region. Cities are spread over regions, so one failure affects only its cities. Rides never cross cities, so ride state needs no cross-region writes. A rider near a border reads two city indexes. Trade-off: A hot city cannot borrow capacity from another region. Over-provision the largest cities.

  • Survives: Declared failover, one writer per city. Copy each city’s ride events to a standby region. On failure, declare a failover and let the standby become the only writer. Replication is asynchronous, so the last seconds of events can be missing: Redis’s own documentation says acknowledged writes can be lost during a failover, and a database copy behaves the same. After failover the new region rebuilds the live index within one location interval, and apps re-fetch ride state. Trade-off: Synchronous copying would remove the gap at the cost of a cross-region delay on every transition. For rides, a rare lost last transition (the app shows the old state and the driver re-taps) is usually cheaper. In CAP and PACELC terms the live index and pushes choose availability, while the ride record stays single-writer and consistent.

A Redis detail that matters for hot cities: a Redis Cluster spreads keys over 16,384 hash slots, and one geo key lives in one slot on one node. Shard the index by cell, such as one key per cell prefix, or a single busy city key becomes the hot node (judgement).

How Does Surge Stay Stable and Fair?#

Pricing is a product decision, but the system still has to compute and apply it. Uber says surge goes into effect when there are more riders in an area than available drivers, with driver availability and rider demand as its primary inputs, and that its teams cap surge in an emergency. Its H3 post says supply and demand are measured per hexagon in each city.

A sketch with made-up numbers: every 60 seconds, per cell, count open requests (demand) and free drivers (supply).

text
raw = demand / supply
  = 120 / 80 = 1.5
smoothed = 0.7 x old + 0.3 x raw
  old 1.2 -> 0.7x1.2 + 0.3x1.5
  = 0.84 + 0.45 = 1.29
capped at 2.5 (assumed)

Smoothing stops the number jumping from 1.2 to 2.0 and back; neighbouring cells can be blended so riders do not see a price cliff across a street. Lock the quoted price when the rider requests, with an expiry, so the fare does not change under them.

The fairness questions are judgement, and an interviewer will probe them: the same price should apply to riders with the same trip at the same moment; emergencies need a cap (as Uber describes); the multiplier should be explained in the app; and higher prices should be a signal to bring in drivers, not a way to hide a shortage.

How Is a Rider Charged Exactly Once?#

Concept in use: Design a rate limiter

The same lesson as the request id: a retry must find the earlier result, not repeat the work.

The flow, in judgement terms: authorize a hold when the ride is requested, capture once when the ride completes, release what was not used. A card hold is built for this: authorize now, capture later, and a partial capture releases the rest. Online card holds usually last about 7 days, with some shorter windows by card network, far longer than a ride.

The danger is a timeout after the provider charged but before you heard back. Send every payment call with an idempotency key derived from the ride, such as ride_id:capture. Stripe, for example, saves the first response for a key and returns it on a retry, rejects the same key with different parameters, and may prune keys after 24 hours.

Because the provider’s window is limited, keep your own ledger: one row per ride and per payment step, written in the same transaction as the completed transition. A retry job reads the ledger, not the provider, and a nightly job compares your ledger with the provider’s records.

FailureWhat happensWhy it is safe
Timeout on captureRetry with the same keyProvider returns the first result
App sends two “end trip”One completed transitionThe version check rejects the second
Retry after 24 hKey may be prunedThe ledger row says captured; no call is made
Capture fails (card declined)Ride is already doneMark the fare owed, retry later, notify the rider

Who Can See Where Everyone Is?#

Location is personal data, and a trail of it says where someone lives and works. These are design rules, not legal advice:

  • Share as little as the trip needs. A rider sees a driver’s position only after a match; a driver sees the pickup only after an offer, and the exact drop-off only after accepting. Any other rider asking for the ride gets a 403.
  • Keep trails for a limited time. GDPR Article 5 asks that data be collected for a specified purpose, limited to what is necessary, and kept in identifiable form no longer than necessary. Keep trip trails for disputes and safety for a set period, then delete or aggregate them.
  • Log staff access to live and historical locations, and limit it by role.
  • Do not trust the phone. GPS is about 4.9 m under open sky and worse near buildings, and spoofing apps exist. Reject a jump that implies an impossible speed (for example, a 300 km/h move between updates, an assumed threshold) and compare the phone’s position with network hints.
  • Safety rules live in the matcher. Uber’s matching page gives one: it prevents matches if one has given the other a one-star rating in the past.

Fraud patterns to name: a driver and rider colluding for fake trips, ghost drivers who sit online without moving, and fake pickups to farm incentives. The usual defences are a velocity check on trips per account, a trip that must show a believable trail, and holding payouts for new accounts (judgement).

Follow-Ups the Interviewer Will Ask#

In an interview

Expect these after the main design; back each answer with a trade-off and a number.

Follow-upStrong short answer
10x traffic (≈ 1.25 million updates a second)Shard the index by cell prefix, add gateways, lengthen the interval when idle. Updates are the load; matching stays small
A driver’s phone loses signal mid-tripKeep the trip; mark the driver stale after the timeout; the trail has a gap and the fare uses the route estimate
Rider cancels right after a matchA conditional transition: allowed from matched, the offer is released, the driver is freed, a fee is a product rule
Driver accepts as the offer expiresBoth are conditional writes on one version; one wins, the other gets 409
A rider has two phonesThe ride is keyed by rider; both apps subscribe to the same events
Airports with queuesA virtual queue per zone; the matcher offers in queue order
Drivers who want trips toward homeAdd a destination filter to the candidate list. Uber says it gives drivers the choice to receive trips toward a destination they set in advance
Pooled ridesOut of scope; matching becomes a route problem, not a pairing
Accept rate suddenly dropsCheck offer quality and the routing ETA first; compare per cell
Learning tip

Cover the right-hand column of the table and say each answer aloud in one breath. Do it again tomorrow without the cover.

The 45-Minute Plan#

Spend the round in this order: 5 minutes for requirements and assumptions, 5 for numbers, API and data model, 10 for the simple design and the geo index, 15 for deep dives (ingest, matching and the claim, ride state), 5 for failures and consistency, and 5 for surge, payments, privacy and wrap-up. If the interviewer pulls you into one deep dive, shorten the others rather than skipping failures.

Try It: A Stadium Empties#

Exercise · 10,000 riders, 1,000 drivers15 min

A concert ends and 10,000 riders ask for a ride within 5 minutes, in an area with about 1,000 drivers. Assume each driver finishes one ride, including the pickup, in 20 minutes, and no new drivers arrive.

  1. What is the request rate, and what is it if each unmatched rider re-requests every 10 seconds?
  2. How long to serve everyone?
  3. What do you change, and what do you tell riders?
Hint

Divide riders by seconds twice, then drivers by 20 minutes for a service rate. Ask what the matcher can do about the second answer.

One reasonable answer
text
10,000 / 300 s        ≈ 33 req/s
10,000 / 10 s retry = 1,000 req/s
1,000 drivers / 20 min
  = 50 rides/min
10,000 / 50 = 200 min

The retry storm, not the first 33 a second, is the real load, so keep requests queued on the server. Matching cannot fix a 200-minute backlog: supply has to come from outside, through nudges to nearby drivers and a higher price that is explained. Match in batches, widen the ring, use a few pickup points and show an honest wait. Pricing is a product decision outside this lesson.

Check Yourself#

Check yourself3 questions · answer, then open
1.The routing service is down. Why keep matching instead of pausing?

A rough ETA from straight-line distance and a detour factor still gets a rider matched, and the ETA is labelled approximate. Pausing would stop every ride. The claim store and ride store are different: without them a double booking is possible, so offers pause.

2.A payment capture times out. Why can the retry not charge twice?

The call carries an idempotency key derived from the ride, so the provider returns the first result. Because provider keys can be pruned after 24 hours, a ledger row per ride also records that the capture happened.

3.Why does a stadium crowd create more load than its request rate suggests?

Unmatched riders re-request. At one retry every 10 seconds, 10,000 waiting riders make 1,000 requests a second against 33 a second at first. Holding requests on the server and using the same request_id removes the storm.

In One Picture#

text
Failures
  safe for claim and money,
  rough is fine for ETA
Hot cell
  batch, queue on server,
  fixed pickups, honest wait
Surge
  smooth, cap, lock the quote
Money
  hold, capture once,
  key on ride id, own ledger
Privacy
  share little, keep briefly,
  log access

Conclusion#

A ride-sharing design earns the first half of the round by being correct. It earns the second half by being survivable: you know what fails, the claim and the money stay safe, and location is treated as data that belongs to someone.

Which failure would worry you most in a service you run: the claim store, the ride store or the payment call? Tell us in the comments.

Where to go from here

Frequently asked questions

What should happen when the Redis claim store fails during matching? +
Stop offering rather than risk two riders getting one driver. Riders stay in the requested state and the matcher retries when the store is back. Rides already matched are unaffected, because the ride record in the durable store is the truth. Redis replication is asynchronous, so a failover can lose claims, which is why the accept is a conditional write on the ride record.
How do you handle thousands of ride requests from one stadium? +
Spread the load instead of retrying harder. Match in short batches so the nearest drivers are shared sensibly, keep waiting riders queued on the server so the app does not re-request every few seconds, use a few fixed pickup points, and give each rider an honest wait estimate. The arithmetic is blunt: 1,000 drivers who each finish a ride in 20 minutes clear 10,000 riders in about 200 minutes, so supply must also come from outside.
How do you keep surge pricing from jumping around? +
Compute demand against supply per area on a short interval, then smooth the multiplier, for example by mixing the new raw value with the old one, and cap it. Lock the price a rider is shown at request time. Uber's description ties surge to riders outnumbering available drivers in an area and says it caps surge in emergencies. The smoothing weights and the cap are product choices.
How do you avoid charging a rider twice? +
Authorize a hold when the ride is requested, capture once at completion, and send every payment call with an idempotency key derived from the ride id. A provider such as Stripe stores the first response for a key and returns it on a retry, and rejects the same key with different parameters. Keep your own ledger row per ride, because provider keys can be pruned after 24 hours.
What should a ride-sharing service do with location data? +
Collect only what the feature needs, show a driver's position to a rider only after a match, keep trip trails for a limited time and log staff access. GDPR Article 5 asks for purpose limitation, data minimisation and storage limitation, which maps to those rules. This is design guidance, not legal advice.

References

  1. Uber: how surge pricing works (supply, demand, emergency caps)
  2. Uber Engineering: H3, Uber's hexagonal hierarchical spatial index
  3. Uber: how batched matching works (and the one-star rule)
  4. Stripe API: idempotent requests (keys, 24-hour pruning, parameter comparison)
  5. Stripe: place a hold on a payment method (authorize now, capture later)
  6. GDPR Article 5: principles for processing personal data
  7. Google SRE Book: Service Level Objectives
  8. Redis documentation: Replication (asynchronous, acknowledged writes can be lost)
  9. Redis documentation: Cluster specification (16384 hash slots, hash tags)
  10. GPS.gov: GPS accuracy (smartphones, open sky versus buildings)
Written by
Sukhveer Kaur
Sukhveer KaurSoftware Developer & AI Engineer

Sukhveer is a software developer specialising in AI systems and backend engineering. She has hands-on experience designing agentic AI applications, working with large language model pipelines, autonomous agent frameworks, and cloud-native services in Java and Python. At InfoWok, she bridges the gap between cutting-edge AI research and practical implementation — helping developers understand and apply emerging technologies through clear, experience-backed writing.

New AI engineering guides, the day they ship

Real Python, production depth. No digest spam.

Comments