Skip to content
LLD Learn/Phase 1 Capstone
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Full Mock Interview: Designing a Ride-Sharing Matching Engine

13 min read

You'll learn to

  • -Work an unfamiliar LLD prompt end to end, live: clarify requirements, design classes, apply patterns, and defend trade-offs
  • -See what separates a strong 45-minute answer from a weak one on a problem outside the case-study list you already studied

Every prior case study in this course was picked because it is a well-known problem you may have already seen the shape of. This chapter deliberately picks a less rehearsed prompt - matching riders to nearby drivers - and works it end to end, live, the way you would actually encounter it: with no prior exposure to the "expected" answer.

Minute 0-5: Clarify Requirements

  • -What triggers a match? A rider requests a ride, and the system finds a nearby available driver.
  • -What does "nearby" mean - do we need real geospatial search, or can we simplify to a reasonable approximation for this exercise?
  • -Can a driver decline a match? If so, does the system need to try the next-nearest driver automatically?
  • -Is pricing/surge in scope, or purely the matching logic? (Assume: matching only, pricing out of scope.)
  • -Single matching request at a time, or do we need to handle many simultaneous ride requests? (Assume: many, concurrently.)

Notice the shape of these questions: each one either changes what gets built or surfaces a hidden concurrency/scale concern. Five minutes spent here is the highest-leverage five minutes of the entire round.

Minute 5-15: Identify Classes and Relationships

The nouns from requirements become classes
class Location:
    def __init__(self, lat: float, lng: float):
        self.lat = lat
        self.lng = lng

class Driver:
    def __init__(self, driver_id: str, location: Location):
        self.driver_id = driver_id
        self.location = location
        self.status: "DriverStatus" = AvailableStatus()

class RideRequest:
    def __init__(self, rider_id: str, pickup: Location):
        self.rider_id = rider_id
        self.pickup = pickup

class Match:
    def __init__(self, ride_request: RideRequest, driver: Driver):
        self.ride_request = ride_request
        self.driver = driver

Driver.status being a DriverStatus object rather than a plain string is a deliberate, up-front decision - it previews the State pattern from Module 6, since "can this driver accept a match" behaves differently across Available, EnRoute, and Offline, exactly the shape that pattern was built for.

Minute 15-30: The Matching Algorithm and Concurrency

The core matching question - "find the nearest available driver" - is itself a swappable algorithm (nearest-by-distance today, but a real system might weigh driver rating, ETA, or surge balancing), which is a clean Strategy opportunity, matching the reasoning from the Strategy chapter directly.

Matching as a Strategy, with atomic driver assignment
class MatchingStrategy(ABC):
    @abstractmethod
    def find_driver(self, request: RideRequest, drivers: list[Driver]) -> Driver | None: ...

class NearestAvailableStrategy(MatchingStrategy):
    def find_driver(self, request: RideRequest, drivers: list[Driver]) -> Driver | None:
        available = [d for d in drivers if isinstance(d.status, AvailableStatus)]
        if not available:
            return None
        return min(available, key=lambda d: distance(d.location, request.pickup))

class MatchingEngine:
    def __init__(self, strategy: MatchingStrategy):
        self._strategy = strategy
        self._lock = threading.Lock()

    def match(self, request: RideRequest, drivers: list[Driver]) -> Match | None:
        with self._lock:                              # atomic: find-and-claim, not find-then-claim
            driver = self._strategy.find_driver(request, drivers)
            if driver is None:
                return None
            driver.status = EnRouteStatus()
            return Match(request, driver)

This is the exact same check-then-act discipline from the Concurrency module and the Parking Lot case study, applied here without being prompted - which is precisely the transfer this course is built to produce: recognizing the shared-mutable-resource shape on a brand-new problem, not just on the problems it was originally taught with.

Minute 30-40: Handling Decline and Retry

When asked "what if the matched driver declines," the answer builds directly on what already exists rather than restarting: MatchingEngine.match() already excludes non-Available drivers, so a decline just needs to set the driver back to AvailableStatus() and re-invoke match() with the same request, excluding the driver who just declined from this specific retry - no new architecture required, only a small addition to existing logic.

Minute 40-45: Trade-offs and What Was Deliberately Left Out

  • -Real geospatial indexing (a quadtree or geohash-based lookup) was skipped in favor of a linear scan over all drivers - correct for a small fleet, and named explicitly as the first thing to fix before real-world scale.
  • -Nothing here is distributed - this is a single-process matching engine, consistent with this course's LLD scope (an HLD round would ask how this scales across regions/servers).
  • -Pricing, ETA calculation, and driver ratings were explicitly scoped out at minute 0, and are named again here as natural next layers.

Closing with a deliberate "here's what I'd improve with more time" list, rather than letting the round just end when the clock runs out, is itself a strong signal - it shows awareness of the design's limits, not just its features.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

ScaleDojo Logo
Initializing ScaleDojo