Compute courier pay and implement load balancing
Company: DoorDash
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Onsite
Quick Answer: This problem evaluates skills in interval arithmetic and aggregation for time-based pay computation alongside routing and load-balancing knowledge covering round-robin and consistent hashing with virtual nodes, testing time-interval handling, rate mapping, hashing, and data-structure design for high-volume inputs.
Part 1: Compute Courier Pay
Constraints
- 0 <= start_minute < end_minute <= 1440
- 0 <= pickup_minute <= dropoff_minute <= 1440
- Rate intervals do not overlap, but they may leave gaps in the day
- 1 <= len(rate_intervals), len(trips) <= 100000 is possible, but the day length is fixed at 1440 minutes
- rate_per_minute and per_trip_bonus are non-negative integers
Examples
Input: ([(0, 60, 2), (60, 120, 1)], [(30, 90, 5)])
Expected Output: 95
Explanation: From minute 30 to 60 the rate is 2 for 30 minutes, and from 60 to 90 the rate is 1 for 30 minutes. Total time pay is 60 + 30, plus bonus 5.
Input: ([(0, 100, 1), (200, 300, 2)], [(50, 250, 10), (260, 280, 0)])
Expected Output: 200
Explanation: First trip pays 50*1 + 100*0 + 50*2 + 10 = 160. Second trip pays 20*2 = 40. Combined total is 200.
Input: ([], [(10, 20, 3)])
Expected Output: 3
Explanation: No pay-rate intervals exist, so time-based pay is 0. Only the trip bonus is earned.
Input: ([(0, 1440, 4)], [])
Expected Output: 0
Explanation: There are no trips, so total pay is 0.
Hints
- Because the day has only 1440 minutes, you can precompute the pay rate for every minute of the day.
- After building per-minute rates, a prefix-sum array lets you answer each trip's time-based pay in O(1).
Part 2: Implement Request Routing with Round Robin
Constraints
- 1 <= len(operations) <= 200000
- serverId and requestId are non-empty strings
- Servers should be routed in the order they were added among currently active servers
- Duplicate addServer operations are ignored
- removeServer on a missing server is ignored
Examples
Input: ([('addServer', 'A'), ('addServer', 'B'), ('route', 'r1'), ('route', 'r2'), ('route', 'r3')],)
Expected Output: ['A', 'B', 'A']
Explanation: With servers A and B active, routing cycles A -> B -> A.
Input: ([('addServer', 'A'), ('addServer', 'B'), ('addServer', 'C'), ('route', 'x'), ('removeServer', 'B'), ('route', 'y'), ('route', 'z')],)
Expected Output: ['A', 'C', 'A']
Explanation: After routing to A, the next server would have been B. Removing B moves the cycle forward to C, then back to A.
Input: ([('route', 'r1'), ('addServer', 'X'), ('removeServer', 'X'), ('route', 'r2')],)
Expected Output: [None, None]
Explanation: There is no active server for either route call.
Input: ([('addServer', 'A'), ('addServer', 'A'), ('addServer', 'B'), ('route', 'p'), ('addServer', 'C'), ('removeServer', 'Z'), ('route', 'q'), ('route', 'r'), ('route', 's')],)
Expected Output: ['A', 'B', 'C', 'A']
Explanation: The second add of A is ignored, removing Z does nothing, and the cycle continues through A, B, C.
Hints
- Think about keeping a pointer to the next server that should receive traffic.
- A hash map plus a circular doubly linked list gives O(1) add, remove, and route.
Part 3: Implement Request Routing with Consistent Hashing
Constraints
- 1 <= len(operations) <= 100000
- 1 <= virtual_nodes <= 1000
- serverId and requestId are non-empty strings
- Use the exact hash function and ring rule described in the statement so routing is deterministic
- Duplicate addServer operations are ignored, and removeServer on a missing server is ignored
Examples
Input: ([('addServer', 'A'), ('addServer', 'B'), ('route', 'Ne'), ('route', 'Oe'), ('route', 'Qe'), ('route', 'zz')], 2)
Expected Output: ['B', 'A', 'B', 'A']
Explanation: With 2 virtual nodes each, the ring positions are A#0=279, B#0=280, A#1=282, B#1=283. The request hashes are Ne=280, Oe=281, Qe=283, zz=366, so routing is B, A, B, then wrap to A.
Input: ([('addServer', 'A'), ('addServer', 'B'), ('route', 'Ne'), ('removeServer', 'B'), ('route', 'Ne'), ('route', 'Oe')], 2)
Expected Output: ['B', 'A', 'A']
Explanation: Before removal, Ne hashes to B. After removing B, both Ne=280 and Oe=281 route to A's next virtual node at 282.
Input: ([('addServer', 'A'), ('route', 'Ne'), ('addServer', 'B'), ('route', 'Ne'), ('route', 'Pe')], 2)
Expected Output: ['A', 'B', 'A']
Explanation: With only A active, Ne routes to A. After adding B, Ne remaps to B's virtual node at 280, while Pe=282 still maps to A.
Input: ([('route', 'x'), ('addServer', 'A'), ('removeServer', 'A'), ('route', 'y')], 3)
Expected Output: [None, None]
Explanation: There are no active servers at either route call.
Hints
- Store all virtual nodes in a sorted structure by hash so you can binary-search the destination for each request.
- Keep track of which virtual-node entries belong to each server so removal is straightforward.