Per-Caller Request Limiter Over Non-Decreasing Time Buckets
Company: Atlassian
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: easy
Interview Round: Technical Screen
You are implementing a simple rate limiter that counts requests in discrete time buckets. Each request belongs to a time bucket, given as an integer, and comes from a caller, identified by a string. A caller may make at most `limit` requests in any one time bucket. Given the requests in the order they arrive, decide for each one whether it is allowed.
The interview built this in two steps, with a few happy-path test cases after each: first a version without callers, in which all requests in a time bucket share one limit, and then the per-caller version below. The first version is the special case in which every request has the same caller.
### Function Signature
```python
def check_requests(limit: int, requests: list[tuple[int, str]]) -> list[bool]:
```
Each request is `(time_bucket, caller_id)`.
### Rules
- Process the requests in the given order. Every request adds 1 to the number of requests its caller has made in its time bucket, whether or not the request is allowed.
- A request is allowed (`True`) if, after it is counted, its caller's number of requests in that time bucket is at most `limit`. Otherwise it is rejected (`False`).
- Counts are kept per caller and per time bucket. Requests from other callers, and requests in other time buckets, never change a caller's count.
- Time buckets never decrease from one request to the next. Consecutive requests may share a bucket, and bucket values may skip numbers.
- Return one boolean per request, in the same order as `requests`.
### Constraints
- `1 <= limit <= 10^9`
- `1 <= len(requests) <= 10^5`
- `0 <= time_bucket <= 10^9` for every request, and the time buckets are non-decreasing in input order
- Every `caller_id` is a nonempty string of at most 20 lowercase English letters and digits.
- All inputs are valid.
### Examples
**Example 1**
```text
Input: limit = 2, requests = [(1, "a"), (1, "a"), (1, "a"), (2, "a")]
Output: [True, True, False, True]
```
Caller `"a"` makes three requests in bucket 1. The third brings the count to 3, which exceeds 2. Bucket 2 starts a new count.
**Example 2**
```text
Input: limit = 1, requests = [(5, "alice"), (5, "bob"), (5, "alice"), (6, "alice"), (6, "bob"), (6, "bob")]
Output: [True, True, False, True, True, False]
```
In bucket 5, `"alice"` and `"bob"` each get one request through, and the second request from `"alice"` is rejected. In bucket 6 both callers start again from zero, and the second request from `"bob"` is rejected.
**Example 3**
```text
Input: limit = 3, requests = [(0, "x"), (0, "x"), (0, "x"), (0, "x"), (0, "x"), (3, "x")]
Output: [True, True, True, False, False, True]
```
This is the single-caller case from the first step. Once the count in bucket 0 exceeds the limit, every further request in that bucket is rejected. Bucket 3 comes directly after bucket 0 in the input, and its count starts from zero.
Overview: A coding question about a rate limiter that receives requests as time bucket and caller pairs, in non-decreasing bucket order, and decides which to allow when each caller may make only a fixed number of requests per bucket. It tests stateful counting, per-key bookkeeping and correct handling of bucket changes.
Read the full Atlassian Software Engineer interview experience this question came from
You are implementing a simple rate limiter that counts requests in discrete time buckets. Each request belongs to a time bucket, given as an integer, and comes from a caller, identified by a string. A caller may make at most `limit` requests in any one time bucket. Given the requests in the order they arrive, decide for each one whether it is allowed.
Implement `check_requests(limit, requests)`. Each element of `requests` is a pair `(time_bucket, caller_id)`. Return a list with one boolean per request, in the same order as `requests`.
### Rules
- Process the requests in the given order. Every request adds 1 to the number of requests its caller has made in its time bucket, whether or not the request is allowed.
- A request is allowed (`True`) if, after it is counted, its caller's number of requests in that time bucket is at most `limit`. Otherwise it is rejected (`False`).
- Counts are kept per caller and per time bucket. Requests from other callers, and requests in other time buckets, never change a caller's count.
- Time buckets never decrease from one request to the next. Consecutive requests may share a bucket, and bucket values may skip numbers.
A rate limiter without callers, in which all requests in a time bucket share one limit, is the special case in which every request has the same caller.
In JavaScript each request is a two-element array `[time_bucket, caller_id]`; in Java it is a `java.util.List<Object>` holding an integer and then a `String`; in C++ it is a `std::pair<int, std::string>`. Every number in the input fits in a signed 32-bit integer (none exceeds 2^31 - 1).
### Example 1
```text
Input: limit = 2, requests = [(1, "a"), (1, "a"), (1, "a"), (2, "a")]
Output: [True, True, False, True]
```
Caller `"a"` makes three requests in bucket 1. The third brings the count to 3, which exceeds 2. Bucket 2 starts a new count.
### Example 2
```text
Input: limit = 1, requests = [(5, "alice"), (5, "bob"), (5, "alice"), (6, "alice"), (6, "bob"), (6, "bob")]
Output: [True, True, False, True, True, False]
```
In bucket 5, `"alice"` and `"bob"` each get one request through, and the second request from `"alice"` is rejected. In bucket 6 both callers start again from zero, and the second request from `"bob"` is rejected.
### Constraints
- `1 <= limit <= 10^9`
- `1 <= len(requests) <= 10^5`
- `0 <= time_bucket <= 10^9` for every request, and the time buckets are non-decreasing in input order
- Every `caller_id` is a nonempty string of at most 20 lowercase English letters and digits.
- All inputs are valid.
Constraints
- 1 <= limit <= 10^9
- 1 <= len(requests) <= 10^5
- 0 <= time_bucket <= 10^9 for every request, and the time buckets are non-decreasing in input order
- Every caller_id is a nonempty string of at most 20 lowercase English letters and digits.
- All inputs are valid.
Examples
Input: (2, [(1, 'a'), (1, 'a'), (1, 'a'), (2, 'a')])
Expected Output: [True, True, False, True]
Explanation: Source Example 1: the third request in bucket 1 brings the count to 3 > 2; bucket 2 starts a new count.
Input: (1, [(5, 'alice'), (5, 'bob'), (5, 'alice'), (6, 'alice'), (6, 'bob'), (6, 'bob')])
Expected Output: [True, True, False, True, True, False]
Explanation: Source Example 2: interleaved callers keep separate counts; both restart from zero in bucket 6.
Hints
- A request's outcome depends only on how many requests share both its caller and its time bucket up to and including it; rejected requests count too.
- Requests from other callers or in other time buckets never change a caller's count, so decide what should identify each count.
- Time buckets never decrease and may skip values, and a new bucket starts every caller again from zero.