The Throttle Ceiling
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Meta
Problem
A public API throttles each client to at most max_requests calls within any window_seconds-long stretch, and a call is accepted only when fewer than max_requests already-accepted calls fall inside the window ending at its arrival; a throttled call is turned away and never counts against later ones. Given calls, the arrival timestamps in arrival order, return a parallel list of booleans that is True for each accepted call and False for each throttled one.
Too many requests in too short a timeframe. Throttle it.
Example 1
Input:
max_requests = 3, window_seconds = 10, calls = [0,3,6,9,11]
Output:
[true,true,true,false,true]
Example 2
Input:
max_requests = 1, window_seconds = 5, calls = [0,4,5]
Output:
[true,false,true]
Worked solution and explanation
Why this problem exists in real interviews
Public APIs need to cap traffic per client without rejecting legitimate burstiness or permanently blacklisting anyone who hits the limit once. Interviewers use this to test class design, the sliding-window vs fixed-window distinction, and one specific trap: do denied requests count toward the next window? They want you to ask, then implement the answer correctly.
Break down the requirements
Step 1: Lock the API the harness expects
The constructor takes max_requests and window_seconds, and the internal decision method is_allowed(timestamp) returns the per-call verdict. The harness drives the whole thing through run_rate_limiter(calls, max_requests, window_seconds), which constructs one limiter, feeds it the timestamps in order, and returns a parallel list of booleans, so that function has to exist and be named exactly that.
Step 2: Evict timestamps outside the rolling window
For each new timestamp T, drop every recorded timestamp t where T - t >= window_seconds. The window is half-open: (T - window, T]. Anything older than the window is gone, anything inside it counts.
Step 3: Allow only when count is below the cap, never record denials
If the remaining count is strictly less than max_requests, append T and return True. Otherwise return False without recording. Recording denied requests would let one bad burst lock the client out forever, since each denial keeps the window full.
The solution
class RateLimiter:
def __init__(self, max_requests, window_seconds):
self.max_requests = max_requests
self.window = window_seconds
self.timestamps = []
def is_allowed(self, timestamp):
self.timestamps = [t for t in self.timestamps if timestamp - t < self.window]
if len(self.timestamps) < self.max_requests:
self.timestamps.append(timestamp)
return True
return False
def run_rate_limiter(calls, max_requests, window_seconds):
limiter = RateLimiter(max_requests, window_seconds)
return [limiter.is_allowed(t) for t in calls]Common follow-up questions
- How does this change when one limiter has to serve millions of independent clients in the same process? (Tests the per-client extension. A dict of `client_id` to deque is the standard answer. Memory grows with active client count, so a follow-up about TTL eviction or LRU pruning is common.)
- Why would a team intentionally pick a fixed-window limiter over the sliding version you wrote? (Tests understanding that fixed windows allow a 2x burst at the boundary (N at the end of one window, N at the start of the next), while sliding windows do not. Counter is simpler and cheaper.)
- How would you make this distributed across N API servers without each server keeping its own count? (Tests Redis primitives. ZADD with timestamp scores plus ZREMRANGEBYSCORE for eviction and ZCARD for the count, wrapped in a Lua script for atomicity. Token bucket via INCR plus EXPIRE is the simpler alternative.)