Implement a Leaky Bucket Rate Limiter
Company: Box
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Onsite
Quick Answer: This question evaluates understanding of rate-limiting algorithms, time-based state management, and concurrent programming concepts, including synchronization and test design for boundary and timing behaviors.
Part 1: Implement a Single-Threaded Leaky Bucket Rate Limiter
Constraints
- 0 <= capacity <= 10^6
- 0.0 <= leak_rate_per_second <= 10^6
- 0 <= len(request_times) <= 2 * 10^5
- 0.0 <= request_times[i] <= 10^9
- request_times is in nondecreasing order
Examples
Input: (3, 1.0, [0.0, 0.2, 0.4, 1.4, 1.5])
Expected Output: [True, True, True, True, False]
Explanation: The bucket leaks between requests. The first four fit after accounting for leakage; the fifth arrives too soon and would exceed capacity.
Input: (2, 1.0, [0.0, 0.0, 1.0, 1.0])
Expected Output: [True, True, True, False]
Explanation: Two requests at time 0 fill the bucket. By time 1.0, one unit has leaked out, so one more request is accepted, but the next same-time request is rejected.
Input: (1, 0.5, [0.0, 1.0, 2.0])
Expected Output: [True, False, True]
Explanation: After 1 second, only 0.5 has leaked, so the second request is rejected. After another second, the bucket drains enough for the third request.
Input: (4, 2.0, [])
Expected Output: []
Explanation: Edge case: no requests.
Input: (0, 3.0, [0.0, 0.5])
Expected Output: [False, False]
Explanation: Edge case: a bucket with capacity 0 can never accept any request.
Hints
- Track the current bucket fill as a floating-point number and reduce it before handling each request.
- Even if a request is rejected, time has still passed, so the last processed timestamp should still be updated.
Part 2: Compute the Behavior of a Thread-Safe Leaky Bucket Rate Limiter
Constraints
- 0 <= capacity <= 10^6
- 0.0 <= leak_rate_per_second <= 10^6
- 0 <= len(thread_ids) == len(request_times) <= 2 * 10^5
- 0 <= thread_ids[i] <= 10^9
- 0.0 <= request_times[i] <= 10^9
- For equal timestamps, calls must be handled in original input order
Examples
Input: (2, 1.0, [10, 11, 12], [0.0, 0.0, 0.0])
Expected Output: [True, True, False]
Explanation: Three concurrent calls hit the same empty bucket with capacity 2. Only the first two can be accepted.
Input: (2, 1.0, [0, 1, 2, 3], [1.0, 0.0, 0.0, 1.0])
Expected Output: [True, True, True, False]
Explanation: Process times in order: indices 1 and 2 at time 0.0 are accepted; by time 1.0 one unit has leaked, so index 0 is accepted and index 3 is rejected.
Input: (1, 0.5, [0, 1, 2], [2.0, 0.0, 1.0])
Expected Output: [True, True, False]
Explanation: Chronological order is index 1, then 2, then 0. The middle request is rejected because only half a unit leaked since the first accepted request.
Input: (3, 5.0, [], [])
Expected Output: []
Explanation: Edge case: no calls from any thread.
Input: (0, 100.0, [7, 8], [0.0, 0.0])
Expected Output: [False, False]
Explanation: Edge case: capacity 0 means every concurrent call is rejected.
Hints
- All threads share one bucket, so do not track state separately per thread.
- Sort requests by (timestamp, original_index), run the same leaky-bucket update as in the single-threaded version, and then write answers back to original positions.