Free preview

API Design

In one line: one parameter in the first call reveals more about the operating environment than the rest of the API combined.

Update driver location

updateDriverLocation(driverID, oldlat, oldlong, newlat, newlong)

The updateDriverLocation API sends the driver's coordinates to the location server. Including oldlat and oldlong simplifies server-side trajectory calculations and enables the service to reconstruct the route even if data packets were queued locally due to network issues. Low-bandwidth clients may omit these fields.

Sending the old position is a design for an unreliable network

Sending where you were along with where you are looks redundant — the server already received the previous update.

Except it may not have. Drivers work in tunnels, underground car parks, and dead zones. Updates get queued on the phone and arrive late, out of order, or not at all.

Carrying the previous position makes each message partially self-describing, which buys three things:

Gap detection. If the arriving message's old position does not match what the server last recorded, an update was lost. The server knows immediately rather than silently interpolating across a hole.

Order independence. Each message describes a segment rather than a point, so a late arrival can be placed correctly in the trajectory instead of appearing as a teleport.

Cheaper trajectory work. The server gets a movement vector directly — direction and speed — without joining against prior state. At 750,000 updates per second, avoiding a lookup per update is not a small saving.

The general principle is worth extracting: on an unreliable channel, make each message describe a transition rather than a state. A state message is only meaningful in sequence; a transition message carries its own context.

This is the same instinct as a database write-ahead log recording before-and-after images, and as distributed logging's argument for self-contained log entries. And note the honest concession — "low-bandwidth clients may omit these fields" — the redundancy costs bytes, and on a bad connection the bytes may matter more than the robustness.

Finding and requesting

findNearbyDrivers(riderID, lat, long)
requestRide(riderID, lat, long, dropOfflat, dropOfflong, typeOfVehicle)
ParameterDescription
lat, longThe rider's current location
dropOfflat, dropOfflongThe rider's drop-off location
typeOfVehicleThe type of vehicle required — business, economy, and so on

findNearbyDrivers has no radius, and requestRide has no radius either

Compare that building block, where every search took an explicit radius. Here neither call does, and that is a meaningful difference rather than an omission.

At Yelp the radius was a user preference — show me cafes within 5 miles. Here it is a system decision, and it must be adaptive:

  • In a dense city, 500 metres may contain forty drivers.
  • In a rural area, the nearest driver may be 15 kilometres away.
  • During the rejection loop from Lesson 4, the radius should expand as attempts fail.

So the server chooses and adjusts it. That is correct — but it means the API cannot express "I'm willing to wait for a cheaper option" or "I need someone within two minutes," both of which are real product features.

typeOfVehicle is more consequential than it looks, too. It partitions the candidate set before proximity is considered, so a request for a premium vehicle searches a much sparser population and needs a much wider radius. A filter applied before a spatial query changes the effective density, which is exactly the problem Yelp's Lesson 10 identified when a category filter thinned a leaf's results.

There is also a missing parameter worth naming: no idempotency key. A rider on a flaky connection who taps "request" and sees nothing may tap again. Without a client-supplied request ID, that is two ride requests and potentially two matched drivers. For an operation this consequential, idempotency should be in the signature.

ETA, pickup, updates, and completion

showETA(rider_lat, rider_lng, destination_lat, destination_lng, ride_type)
confirmPickup(driverID, riderID, timestamp)
showTripUpdates(tripID, riderID, driverID, driverlat, driverlong, time_elapsed, time_remaining)
endTrip(tripID, riderID, driverID, time_elapsed, lat, long)

showTripUpdates is not a request-response call

Every other entry in this API is a request that returns. This one "streams real-time trip data" — the server pushes updated positions and remaining time continuously for the duration of the trip.

Writing it in the same function-call notation as endTrip hides that, and the difference matters: it runs over the WebSocket connection from Lesson 4, it has no single response, and its cost is a held connection for tens of minutes rather than a request.

Note also that time_remaining is described as "extract the current time from the ETA" — so it is recomputed continuously as traffic changes, not fixed at trip start. That makes it a live output of the ETA service rather than a stored field, and it is why Lesson 9's ETA service is on the trip path and not just the matching path.

confirmPickup and endTrip take a client-supplied timestamp, and that is a money problem

confirmPickup(driverID, riderID, timestamp) — the driver's device reports when the trip started. endTrip similarly carries time_elapsed.

Those values determine the fare. And they come from a device controlled by the party being paid.

Lesson 13's fraud list names exactly this attack: "deliberately increasing trip time or distance" and "manipulating GPS data or using fake location apps." The API as specified hands the adversary the input.

The fix is standard and cheap: the server stamps the time. The client reports the event; the server records when it learned of it. Clock skew and network delay make the client's own clock unusable for billing regardless, quite apart from tampering.

Where the client timestamp is genuinely useful is reconciling offline events — a pickup confirmed in a tunnel and synced three minutes later. So accept it, but treat it as a claim to be validated against the server's own record and the location stream, not as authoritative.

Never let the party who benefits from a value be its only source. That is the general rule, and it applies to time_elapsed in endTrip for the same reason.

endTrip is the only call that moves money, and it should be idempotent

"The endTrip API marks the trip as completed" — and per Lesson 12, trip completion is the business event that triggers fare computation and payment.

So a duplicate endTrip, from a retry on a flaky network, is a duplicate charge. Lesson 12's list of things to prevent names it explicitly.

The signature has no idempotency key, so protection has to come from the trip state machine: COMPLETED is terminal, and a second endTrip on an already-completed trip must be a no-op returning success, not an error and certainly not a second charge.

That is worth stating as a general property: the state machine is what makes the API idempotent. Any transition that is already satisfied should succeed silently. It is the cheapest available defence against exactly-once delivery being impossible.

The two halves of this API have almost nothing in common operationally: one is a firehose of tiny idempotent writes, the other a small number of long-lived stateful transactions. Sizing them together is the estimation mistake.

Key takeaway

updateDriverLocation carrying old and new coordinates is the chapter's most interesting parameter — it makes each message describe a transition rather than a state, which buys gap detection, order independence, and cheaper trajectory computation on an unreliable network. Neither find nor request call takes a radius, because here it is a system decision that must expand during the rejection loop — and typeOfVehicle filters before proximity, changing the effective density. showTripUpdates is a stream, not a call. And confirmPickup/endTrip take client-supplied timestamps that determine the fare — the server should stamp time itself and treat the client's claim as evidence, because you never let the party who benefits from a value be its only source.

Next: the location manager, and the write pressure it exists to absorb.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue