Sign in to run and submit your work
Reading is open to everyone. Running code and saving drafts need an account so your work is yours and comes back on your next visit.
or
CODE WORKSPACE
The ingestion API is shared by every tenant, and one of them regularly bursts hard enough to slow everybody down. Before turning on throttling, the team wants to replay a day of requests through the proposed limit and see who would have been refused.
Write rate_limit(requests, capacity, refill_per_second). Each request carries an id, a key and a timestamp in seconds.
Function to write
rate_limit(requests: list[dict], capacity: int, refill_per_second: float) -> list[dict]One decision per request in processing order, with the key, the verdict, the tokens left and a reason.
How to approach it
Refill from the elapsed time since that key's last request, then cap at the capacity.
Sample cases
+ 2 held back until you submit
one noisy tenant
One tenant burns its bucket while another barely uses its own, and the feed is out of time order.
Input
Argument 1
| id | key | at |
|---|---|---|
| r1 | tenant_a | 0 |
| r2 | tenant_a | 0.1 |
| r3 | tenant_b | 0.2 |
| r4 | tenant_a | 0.2 |
| r5 | tenant_a | 0.3 |
| r6 | tenant_a | 3 |
| r7 | tenant_b | 0.1 |
Argument 2
3Argument 3
1Returns
| id | key | allowed | tokens_left | reason |
|---|---|---|---|---|
| r1 | tenant_a | True | 2 | None |
| r2 | tenant_a | True | 1.1 | None |
| r7 | tenant_b | True | 2 | None |
| r4 | tenant_a | True | 0.2 | None |
| r3 | tenant_b | True | 1.1 | None |
| r5 | tenant_a | False | 0.3 | None |
| r6 | tenant_a | True | 2 | None |
no requests
An idle window produces no decisions rather than an empty allowance.
Input
Argument 1
[] (empty list)
Argument 2
3Argument 3
1Returns
[] (empty list)
a bucket that never refills
With no refill the bucket is a total quota, so a tenant is cut off permanently.
Input
Argument 1
| id | key | at |
|---|---|---|
| r1 | tenant_a | 0 |
| r2 | tenant_a | 0.1 |
| r3 | tenant_b | 0.2 |
| r4 | tenant_a | 0.2 |
| r5 | tenant_a | 0.3 |
| r6 | tenant_a | 3 |
| r7 | tenant_b | 0.1 |
Argument 2
2Argument 3
0Returns
| id | key | allowed | tokens_left | reason |
|---|---|---|---|---|
| r1 | tenant_a | True | 1 | None |
| r2 | tenant_a | True | 0 | None |
| r7 | tenant_b | True | 1 | None |
| r4 | tenant_a | False | 0 | None |
| r3 | tenant_b | True | 0 | None |
| r5 | tenant_a | False | 0 | None |
| r6 | tenant_a | False | 0 | None |
Constraints
capacity tokens.refill_per_second, and never past capacity.unusable_timestamp, and does not touch any bucket.Worked example
With a capacity of 3 and one token per second, tenant_a sends four requests in the first three tenths of a second. The first three are allowed and drain the bucket to nearly zero — the refill over 0.1 seconds is 0.1 tokens, which is not enough — so the fourth is refused. By 3.0 seconds the bucket has refilled and tenant_a is allowed again.
tenant_b sends two requests in the same window and is never refused, because it has its own bucket. A single shared bucket would have throttled tenant_b for tenant_a's behaviour, which is the exact failure the limiter is meant to prevent — and it is what almost every first implementation does.
What this tests
Continuous state derived from elapsed time rather than from a counter that somebody has to reset, and per-key isolation. The cap on refill is what makes a bucket a burst allowance instead of an unbounded credit.
rate_limit(requests: list[dict], capacity: int, refill_per_second: float) -> list[dict]Submit for review to find out what your query gets right, what it gets wrong, and how it compares with the best working query for this exercise.
This scenario runs a full workspace — editor, canvas and results side by side. It needs a laptop or desktop to be usable. Open this page on a bigger screen to start building.