Katkat

Filter Open Restaurants Through Existing Domain APIs

DoorDashSoftware Engineer · Technical Screen

Last updated

Implement an orchestrator service that coordinates calls to a directory API and an operating-hours API with bounded concurrency, short-circuit filtering, resilient error handling, and in-memory caching.

Problem Statement

At DoorDash, presenting an accurate feed of open restaurants requires coordinating multiple downstream microservices. You are tasked with implementing the core orchestration logic for a service that retrieves and filters restaurants for a given city at a specific Unix timestamp.

You are provided with two external domain client interfaces:

  1. DirectoryClient

    • get_restaurants_by_city(city: str) -> list[dict]
    • Returns restaurant summaries: [{"id": str, "name": str, "city": str, "active": bool}, ...]
  2. HoursClient

    • is_open(restaurant_id: str, timestamp_utc: int) -> bool
    • Returns True if the store is open at the given timestamp, False otherwise. This call is network-intensive and may raise exceptions (e.g., TimeoutError or ConnectionError).

Implement the class OpenRestaurantFinder with the following requirements:

  • Pre-filtering & Short-circuiting: Do not call HoursClient for restaurants where active is False or whose city does not match the requested city (case-insensitive comparison).
  • Bounded Concurrency: Query the HoursClient concurrently for valid candidates using at most max_workers parallel workers.
  • Failure Handling: If HoursClient.is_open raises an error for any restaurant, treat that restaurant as closed (False) rather than failing the entire request.
  • Order Preservation: The returned list of open restaurants must maintain the relative ordering provided by the DirectoryClient.
  • Safe In-Memory Caching: Cache the boolean result of (restaurant_id, timestamp_utc) calls in memory so repeated requests with identical parameters do not make duplicate downstream calls to HoursClient.

Class Interface

class OpenRestaurantFinder:
    def __init__(self, directory_client, hours_client, max_workers: int = 5):
        """
        Initialize with downstream API clients and concurrency limit.
        """
        pass

    def get_open_restaurants(self, city: str, timestamp_utc: int) -> list[dict]:
        """
        Returns only active, open restaurants for the specified city at timestamp_utc,
        preserving original ranking order.
        """
        pass

Constraints

  • 1 = 0
  • 1 <= max_workers <= 20
  • Number of restaurants returned by directory: 0 <= N <= 10,000
  • The cache should be thread-safe if your implementation uses multi-threading.

Examples

Example 1

Directory response for city = "San Francisco":

[
    {"id": "r1", "name": "Tartine", "city": "San Francisco", "active": True},
    {"id": "r2", "name": "Bob's Donuts", "city": "San Francisco", "active": False},
    {"id": "r3", "name": "Arsicault", "city": "San Francisco", "active": True},
    {"id": "r4", "name": "Che Fico", "city": "Oakland", "active": True}
]

HoursClient.is_open(restaurant_id, 1700000000) results:

  • "r1": True
  • "r3": False
  • "r2" and "r4" should never be queried because "r2" is inactive and "r4" is in Oakland.

Output:

[
    {"id": "r1", "name": "Tartine", "city": "San Francisco", "active": True}
]

Example 2 (Downstream Failure)

Directory response for city = "Seattle":

[
    {"id": "s1", "name": "Pike Bagels", "city": "Seattle", "active": True},
    {"id": "s2", "name": "Canlis", "city": "Seattle", "active": True}
]

HoursClient.is_open calls:

  • "s1" raises TimeoutError
  • "s2" returns True

Output:

[
    {"id": "s2", "name": "Canlis", "city": "Seattle", "active": True}
]

Hints

Hint 1

Filter out inactive or out-of-city entries synchronously before dispatching async or threaded workers to minimize network calls.

Hint 2

Map futures back to their original index in the pre-filtered candidate list so you can preserve the original ordering despite out-of-order worker completion.

Hint 3

Protect in-memory cache writes with a mutex lock (or use thread-safe data structures) when running concurrent requests.

Follow-up questions

  • How would you implement a Time-To-Live (TTL) eviction policy for the cache to prevent memory bloat in production?
  • If the HoursClient is consistently timing out across multiple stores, how would you incorporate a circuit breaker pattern to shed load?
  • How would you bucket timestamps (e.g., 5-minute windows) to increase cache hit rates without sacrificing opening/closing accuracy?