Debug the Last-Element Random Allocation Case
DoorDashSoftware Engineer · OnsiteLast updated
Implement and test an O(1) randomized pool that avoids and deterministically tests against the classic self-swap regression when removing the tail element.
Problem Statement
DoorDash dynamically matches incoming delivery orders with available active couriers. To balance distribution, an internal dispatcher maintains an active courier pool with three operations that must run in average O(1) time:
add(courier_id: int) -> bool: Adds a courier to the pool. ReturnsTrueif the courier was newly added, orFalseif they were already present.remove(courier_id: int) -> bool: Removes a courier from the pool. ReturnsTrueif the courier was successfully removed, orFalseif they were not in the pool.sample() -> int: Returns a uniformly randomcourier_idfrom the current pool. If the pool is empty, it should raise anIndexError.
A common pattern for O(1) removal maintains an array of items alongside a hash map of {item: array_index}. The target item is swapped with the last element in the array, the index entry is updated, and the array is popped.
However, a notoriously subtle bug occurs when the item to be removed is already the last element (or the only element) in the array. If the update order is incorrect (e.g., updating the map for the "moved" last element after deleting the target, or overwriting indices improperly), the map can resurrect a deleted key or store an out-of-bounds index.
Your task is to:
- Implement the
CourierPoolclass correctly handling all edge cases in averageO(1)time. - Provide a deterministic test function
test_last_element_edge_cases()that isolates and validates the self-swap / tail-element removal scenarios without relying on non-deterministic random assertions.
Class Interface
class CourierPool:
def __init__(self):
pass
def add(self, courier_id: int) -> bool:
pass
def remove(self, courier_id: int) -> bool:
pass
def sample(self) -> int:
pass
Examples
Example 1
pool = CourierPool()
pool.add(101) # Returns True
pool.add(102) # Returns True
pool.remove(102) # Removing the tail element! Returns True
pool.sample() # Returns 101 with 100% probability
pool.remove(101) # Removing the only remaining element (also tail). Returns True
pool.add(102) # Re-adding previously removed element. Returns True
Example 2
pool = CourierPool()
pool.add(42) # Returns True
pool.remove(42) # Returns True
pool.remove(42) # Returns False (already removed)
Constraints
-2^31 <= courier_id <= 2^31 - 1- At most
2 * 10^5total calls will be made across all methods. - Each method must execute in
O(1)average time. - Total space complexity should be
O(N), whereNis the number of distinct couriers currently in the pool.
Hints
Hint 1
Identify what happens in your removal logic when idx == len(arr) - 1: if you overwrite map[last_val] before deleting map[target], last_val and target are the same key.
Hint 2
The safest sequence is to fetch the last element, move it into arr[idx], update map[last_val] = idx, and then pop arr and delete target from the map (or handle idx == len(arr) - 1 as a clean pop-and-delete).
Hint 3
For deterministic testing, assert directly on internal state invariants: map size must match array size, and array[map[id]] == id for every stored key after each removal.
Follow-up questions
- How would you adapt this data structure if each courier has a non-uniform weight (e.g., acceptance rate) that changes dynamically?
- If multiple worker threads call sample() concurrently while add() and remove() run in the background, what concurrency control mechanisms would maintain O(1) performance?
- How would you implement a bulk_remove(courier_ids) method efficiently when a large batch of couriers go offline simultaneously?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.