Rate Limiter: preserve quota under concurrent callers
Requirements
Implement TokenBucket(capacity, refill_per_second, clock).allow(). clock() returns seconds. Refill from elapsed time, cap at capacity, and consume one token atomically. Concurrent callers must never consume more than the available tokens. The injected clock makes time deterministic.
E: Expected execution (provided)
The course supplies the target execution before you design the solution. This is the observable acceptance example your implementation must satisfy. It intentionally shows the API surface the caller expects, including class names, constructors, methods, arguments, return values, and collaborator shapes where those are part of the requirement.
It is target behavior, not starter implementation. Some names used below may not exist in the starter yet because creating the required design is your task.
now = [0.0]
bucket = TokenBucket(2, 1.0, lambda: now[0])
assert bucket.allow() is True
assert bucket.allow() is True
assert bucket.allow() is False
now[0] = 1.0
assert bucket.allow() is True
The visible functional test uses this same acceptance harness. Your final design must make this expected execution pass unchanged. Do not rewrite the acceptance example to fit your implementation.
Start reasoning at S
E is already established by the course. Before opening hints, reason through SCOAT in plain language:
- S: Stable roles: Which enduring responsibility/capability must callers be able to rely on?
- C: Change: What actually varies now or under the stated design pressure?
- O: Ownership: Who owns the rule/state, who creates it, and what lifetime/scope matters here?
- A: Abstraction need: Does the pressure genuinely require substitution, isolation, extension, or translation? No additional abstraction is a valid answer.
- T: Topology: Who contains, calls, or delegates to whom at runtime, in what order, and along which failure path?
A checkpoint may legitimately produce no code change. A stable role does not mean "make an interface"; use the simplest Python boundary that preserves the behavior and design property.
Design-stress contract
First satisfy the ordinary behavior. Then run the design-stress checks. They change the pressure without prescribing class names, inheritance syntax, Protocol usage, or a particular AST shape.
- Capacity is atomic under a burst: Twenty callers hit a capacity-five bucket at the same instant with no refill. Expected: Exactly five calls are allowed.
A solution is considered complete only when both ordinary behavior and the intended maintainability property survive. The tests are allowed to reject a functionally-correct but brittle implementation.
S + C: stable role and change
๐ก S + C: stable role and change
Stable role: The limiter exposes one atomic allow decision. Change pressure: Capacity/rate/clock can vary; token invariant remains. Do not turn the role into an interface unless substitution or isolation actually needs one.
Suggested Workspace Changes (1 file)
O + A: ownership and minimum boundary
๐ก O + A: ownership and minimum boundary
Ownership/lifetime: The clock is injected; token state and lock live exactly as long as the bucket. Smallest structural consequence: Clock, token state, and lock belong to one bucket. Add only the boundary justified by this pressure; in other exercises, no additional abstraction may be correct.
Suggested Workspace Changes (1 file)
T: trace the runtime topology under pressure
๐ก T: trace the runtime topology under pressure
Runtime topology: lock โ refill from elapsed time โ decide/consume โ unlock. Adversarial change: Twenty callers hit a capacity-five bucket at the same instant with no refill. The target property is Thread-safe token consumption. Expected behavior: Exactly five calls are allowed. Change only the ownership, dependency, interaction, or variation boundary needed to make that property true.
Reveal and apply a complete reference solution
๐ก Reveal and apply a complete reference solution
Use this only after you have attempted the design. The platform will preview the exact diff before confirmation; Apply Hint Changes replaces src/solution.py with a complete reference implementation that satisfies ordinary behavior and the design-stress contract.