Design the rider app of a ride-hailing service: requesting, matching, a live driver on the map, trip states and a bad network.

26.8.6 · L5 · Interview

Reported at: Uber

Predict first

What do the trip reducer, the position tracker and the glide print?

enum Trip: Equatable {
    case requesting, matched(driver: String), arriving(etaMinutes: Int), onTrip, completed(fareCents: Int), cancelled
}
enum TripEvent { case matched(String), eta(Int), pickedUp, dropped(fareCents: Int), cancel }

func reduce(_ s: Trip, _ e: TripEvent) -> Trip {
    switch (s, e) {
    case (.requesting, .matched(let d)): return .matched(driver: d)
    case (.matched, .eta(let m)), (.arriving, .eta(let m)): return .arriving(etaMinutes: m)
    case (.matched, .pickedUp), (.arriving, .pickedUp): return .onTrip
    case (.onTrip, .dropped(let fare)): return .completed(fareCents: fare)
    case (.requesting, .cancel), (.matched, .cancel), (.arriving, .cancel): return .cancelled   // no free cancel once the trip started
    default: return s
    }
}
struct DriverPosition { let seq: Int; let x: Double; let y: Double }
struct Tracker {
    private(set) var latest: DriverPosition?
    private(set) var dropped = 0
    mutating func apply(_ p: DriverPosition) {
        if let l = latest, p.seq <= l.seq { dropped += 1; return }     // stale or duplicate: never move the car backwards
        latest = p
    }
}
func lerp(_ a: DriverPosition, _ b: DriverPosition, _ t: Double) -> (Double, Double) {
    (a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)                      // glide the marker between two fixes
}
var trip = Trip.requesting
for e: TripEvent in [.matched("Asha"), .eta(4), .eta(2), .pickedUp, .cancel, .dropped(fareCents: 1850)] {
    trip = reduce(trip, e); print(trip)
}
var t = Tracker()
for seq in [1, 3, 2, 3, 4] { t.apply(DriverPosition(seq: seq, x: Double(seq), y: 0)) }
print("latest seq:", t.latest!.seq, "dropped:", t.dropped)
print(lerp(DriverPosition(seq: 1, x: 0, y: 0), DriverPosition(seq: 2, x: 10, y: 4), 0.5))
  • matched(driver: "Asha") / arriving(etaMinutes: 4) / arriving(etaMinutes: 2) / onTrip / onTrip / completed(fareCents: 1850) / latest seq: 4 dropped: 2 / (5.0, 2.0)
  • cancelled after the cancel event
  • latest seq: 3 dropped: 0

Mental model. A delivery tracker: the parcel’s status only moves forward, and each position ping carries a number so a late ping from five minutes ago never moves the dot back along the road.

Reported for Uber (a search-result summary; the page itself could not be read, so treat it as a typical prompt). Model the trip as a server-driven state machine in the client: requesting, matched, arriving (with an ETA), on trip, completed (with the fare) and cancelled. A pure reduce(state, event) ignores events that do not fit the state; the check shows the rule that a cancel after pickup is refused (the state stays onTrip) and the trip still completes with its fare. The server is the source of truth: on launch or after a network gap, ask it for the current trip and rebuild the screen from that, so an app kill mid-trip loses nothing.

The live driver position is the hard part. Updates arrive over a persistent channel (a WebSocket or similar, with push as the wake-up; see the transport question) and can be late, duplicated or out of order, so every position carries a sequence number and the client drops anything not newer than the latest: after receiving 1, 3, 2, 3, 4 the latest is 4 and two updates were dropped, so the car never jumps backwards. Between fixes, interpolate the marker (a straight-line glide between two points) and snap on a large gap, because moving a map marker only on each update looks jerky. Also name: location permission and accuracy for the pickup point, battery use (only stream while a trip is active), degraded behaviour offline (show the last position with its age), map tiles and their cache (see the map-tile question), price shown before confirming, and safety features. Only the state and tracking logic runs here.

Illegal events are ignored (no cancel after pickup), stale and duplicate positions are dropped, and the midpoint of the glide is halfway between the two fixes.

Common traps

  • Trusting update order. Without sequence numbers a delayed fix can move the car backwards.
  • Keeping the trip state only in memory. A force quit mid-ride must not lose the ride; ask the server on launch.

They might follow up with

  • How do you save battery during a long trip? Lower the update rate when the driver is far, and stop streaming when the trip ends.
  • What if the connection drops during the trip? Show the last known position and its age, reconnect with backoff, and ask the server for the latest state and position.

Related questions

  • 26.2.1 How do you design a client for reliability: deadlines, circuit breakers, degradation tiers, bulkheads, kill…
  • 26.8.2 How does the client learn about new data: polling, long polling, WebSocket, server-sent events or push? How…
  • 26.5.3 Design a map or ride-hailing client: tile maths, caching while panning, location updates, battery and what…
  • 26.8.3 Design the client of a flight booking app: search, seat hold, payment and confirmation. How do you handle a…

← Design an image browsing app (a grid of photos you can open full…   Design the API behind a photo feed: pagination, response shape, image… →