Quick Overview

A coding question about an in-memory rate limiter that lets at most a fixed number of requests through per second. Given non-decreasing request timestamps in milliseconds, decide whether each request is accepted, testing exact one-second window boundaries, same-timestamp bursts, and the rule that rejected requests consume no capacity.

In-Memory Per-Second API Rate Limiter: Accept or Reject Each Request

Company: Patreon

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

An API endpoint is protected by an in-memory rate limiter. The limiter is created with one value, `max_per_second`, the largest number of requests it lets through per second. Every request to the endpoint first asks the limiter whether it still has capacity, and the limiter accepts or rejects the request. Implement the limiter's decisions: given the arrival times of the requests, return whether each one is accepted. ### Function Signature ```python def rate_limit(timestamps: list[int], max_per_second: int) -> list[bool]: ``` `timestamps[i]` is the arrival time of request `i` in milliseconds. The result has one entry per request, in input order: `True` if the request is accepted and `False` if it is rejected. ### Rules - Requests are decided one at a time in list order, and `timestamps` is non-decreasing. Several requests may share a timestamp. - A request arriving at time `t` is accepted if fewer than `max_per_second` earlier requests were accepted with a timestamp `s` satisfying `t - 1000 < s <= t`. Otherwise it is rejected. - A request accepted exactly 1000 ms before `t` no longer counts against the request at `t`. - Rejected requests use no capacity: they never count against later requests. ### Constraints - `1 <= len(timestamps) <= 2 * 10^5` - `0 <= timestamps[i] <= 10^9` - `1 <= max_per_second <= 10^5` ### Examples **Example 1** The times are written in milliseconds after 09:30:00.000, so 09:30:05.250 is `5250`. ```text Input: timestamps = [5250, 5400, 5700, 6250, 6300, 6400], max_per_second = 2 Output: [True, True, False, True, False, True] ``` At 5700, the requests at 5250 and 5400 were both accepted within the last second, so the limit of 2 is reached. At 6250, the request at 5250 is exactly 1000 ms old and no longer counts; only 5400 counts, so the request is accepted. At 6300, the accepted requests at 5400 and 6250 count, so it is rejected. At 6400, the request at 5400 is exactly 1000 ms old; only 6250 counts, so it is accepted. **Example 2** ```text Input: timestamps = [0, 0, 999, 1000, 1000, 2500], max_per_second = 1 Output: [True, False, False, True, False, True] ``` The second request at 0 and the request at 999 both see the accepted request at 0. The first request at 1000 does not, because that request is exactly 1000 ms old, so it is accepted, and the second request at 1000 then sees it. At 2500 nothing accepted is recent enough to count. **Example 3** ```text Input: timestamps = [100, 200, 300, 400, 1150, 1250, 1350], max_per_second = 3 Output: [True, True, True, False, True, True, True] ``` The request at 400 is rejected because 100, 200 and 300 were accepted. At 1150 the accepted requests that count are 200 and 300; the rejected request at 400 uses no capacity, so 1150 is accepted. At 1250 the requests at 300 and 1150 count, and at 1350 the requests at 1150 and 1250 count, so both are accepted.

Overview: A coding question about an in-memory rate limiter that lets at most a fixed number of requests through per second. Given non-decreasing request timestamps in milliseconds, decide whether each request is accepted, testing exact one-second window boundaries, same-timestamp bursts, and the rule that rejected requests consume no capacity.

Read the full Patreon Software Engineer interview experience this question came from

An API endpoint is protected by an in-memory rate limiter. The limiter is configured with a single value, `max_per_second`, the largest number of requests it lets through per second. Every request to the endpoint first asks the limiter whether it still has capacity, and the limiter either accepts or rejects the request. Implement the limiter's decisions: given the arrival times of the requests, return whether each one is accepted. ### Function `rate_limit(timestamps, max_per_second)` - `timestamps[i]` is the arrival time of request `i`, in milliseconds. - Return a list with one boolean per request, in input order: `True` if the request is accepted and `False` if it is rejected. ### Rules - Requests are decided one at a time in list order. `timestamps` is non-decreasing, and several requests may share the same timestamp. - A request arriving at time `t` is accepted if fewer than `max_per_second` earlier requests were accepted with a timestamp `s` satisfying `t - 1000 < s <= t`. Otherwise it is rejected. - A request accepted exactly 1000 ms before `t` no longer counts against the request at `t`. - Rejected requests use no capacity: they never count against later requests. No input value exceeds 2^31 - 1, so 32-bit integers suffice in every language. ### Example 1 ```text Input: timestamps = [5250, 5400, 5700, 6250, 6300, 6400], max_per_second = 2 Output: [True, True, False, True, False, True] ``` At 5700, the accepted requests at 5250 and 5400 are both within the last second, so the limit of 2 is reached and the request is rejected. At 6250, the request at 5250 is exactly 1000 ms old and no longer counts; only 5400 counts, so the request is accepted. At 6300, the accepted requests at 5400 and 6250 count, so it is rejected. At 6400, the request at 5400 is exactly 1000 ms old; only 6250 counts, so it is accepted. ### Example 2 ```text Input: timestamps = [0, 0, 999, 1000, 1000, 2500], max_per_second = 1 Output: [True, False, False, True, False, True] ``` The second request at 0 and the request at 999 both see the accepted request at 0. The first request at 1000 does not, because that request is exactly 1000 ms old, so it is accepted; the second request at 1000 then sees it. At 2500, no accepted request is recent enough to count. ### Constraints - `1 <= len(timestamps) <= 2 * 10^5` - `0 <= timestamps[i] <= 10^9`, and `timestamps` is non-decreasing (duplicates allowed) - `1 <= max_per_second <= 10^5`

Constraints

  • 1 <= len(timestamps) <= 2 * 10^5
  • 0 <= timestamps[i] <= 10^9
  • timestamps is non-decreasing; several requests may share a timestamp
  • 1 <= max_per_second <= 10^5

Examples

Input: ([5250, 5400, 5700, 6250, 6300, 6400], 2)

Expected Output: [True, True, False, True, False, True]

Explanation: Source Example 1: requests exactly 1000 ms old (5250 at 6250, 5400 at 6400) stop counting.

Input: ([0, 0, 999, 1000, 1000, 2500], 1)

Expected Output: [True, False, False, True, False, True]

Explanation: Source Example 2: max_per_second = 1 with shared timestamps 0 and 1000; the accepted request at 0 is exactly 1000 ms old at 1000.

Hints

  1. Only accepted requests influence later decisions; once a request is rejected it can be forgotten.
  2. Timestamps never decrease, so ask whether an accepted request that has stopped counting could ever count again for a later request.
  3. Check the window edges against the rules: an accepted request exactly 1000 ms old does not count, while one 999 ms old or one at the same millisecond does.

Loading coding console...

Show the approach

Approach

Keep the accepted timestamps, in arrival order, in a buffer with a head index marking the oldest entry that may still count. Because timestamps are non-decreasing, an accepted request with s <= t - 1000 can never count against the current request or any later one, so before deciding request t, advance head past every such entry; this implements the rule that a request accepted exactly 1000 ms earlier no longer counts. Invariant: after eviction, the entries between head and the end of the buffer are exactly the accepted requests with t - 1000 < s <= t (s <= t holds automatically because they arrived earlier in a non-decreasing list), so their number is the count the rule compares against max_per_second. If that count is below max_per_second the request is accepted and t is appended; otherwise it is rejected and nothing is appended, so rejected requests never use capacity. Each timestamp is appended at most once and passed by head at most once, so the total work is linear. Edge cases: a single request is always accepted; accepted requests at the same millisecond count against each other; several accepted requests can leave the window at the same step; an idle gap of 1000 ms or more evicts every earlier accepted request; all values are at most 10^9 and fit in 32-bit integers.

Time complexity:
O(n)
Space complexity:
O(n)