Make Token-Bucket Admission Safe Across Threads
Charta HealthSoftware Engineer · Technical ScreenLast updated
Tests your ability to implement a thread-safe token bucket rate limiter with lazy refill, monotonic time tracking, and blocking admission control.
Problem Statement
In high-throughput distributed healthcare platforms, background jobs and external API webhooks frequently compete for restricted upstream endpoints. To prevent overwhelming downstream services, incoming requests must pass through a strict rate-limiting admission controller.
Implement a thread-safe TokenBucket class that regulates admissions using the standard token-bucket algorithm. Rather than running a background worker thread to continuously deposit tokens, the bucket should refill tokens lazily whenever admission is attempted, using a monotonic clock.
Class Interface
Implement the following methods:
class TokenBucket:
def __init__(self, capacity: int, refill_rate: float):
"""
capacity: maximum number of tokens the bucket can hold (integer > 0).
refill_rate: tokens added per second (float > 0.0).
The bucket starts full at initialization.
"""
pass
def try_acquire(self, tokens: int = 1) -> bool:
"""
Attempts to consume `tokens` immediately without blocking.
Returns True if tokens were acquired, False otherwise.
"""
pass
def acquire(self, tokens: int = 1, timeout: float = None) -> bool:
"""
Blocks until `tokens` are available or until `timeout` seconds elapse.
If `timeout` is None, blocks indefinitely until tokens can be acquired.
Returns True if acquired within the timeout, False if the timeout expired.
"""
pass
Requirements
- Thread Safety: Multiple threads will call
try_acquireandacquireconcurrently. All state mutations must be synchronized to prevent race conditions such as over-allocation or lost updates. - Monotonic Time: Time calculations must rely on a steady, monotonic clock (
time.monotonic()in Python) to avoid skew from system wall-clock adjustments. - Efficient Waiting: In
acquire, threads should wait on a condition variable or equivalent synchronization primitive rather than busy-waiting / polling in a loop. - Refill Cap: Tokens accumulate continuously proportional to elapsed time, but the bucket must never exceed
capacity.
Constraints
1 <= capacity = 0.0).
Example
bucket = TokenBucket(capacity=5, refill_rate=2.0) # Starts with 5.0 tokens
# Immediate consumption
bucket.try_acquire(3) # Returns True (2.0 tokens remain)
bucket.try_acquire(3) # Returns False (only 2.0 tokens available)
# Blocking consumption with timeout
# 1 token needed to reach 3; at 2.0 tokens/sec, requires 0.5s wait
bucket.acquire(3, timeout=0.2) # Returns False after 0.2s (timed out)
bucket.acquire(3, timeout=1.0) # Waits ~0.5s, acquires, returns True
Hints
Hint 1
Refill tokens on-demand before checking availability by calculating `(now - last_refill_time) * refill_rate` and updating `last_refill_time`.
Hint 2
Protect both the token count and `last_refill_time` under a single mutual-exclusion lock, and use a condition variable to wake waiting threads when tokens replenish.
Hint 3
When waiting in `acquire`, compute the exact required sleep duration for the missing tokens instead of waking up blindly or spin-locking.
Follow-up questions
- How does your implementation prevent 'thundering herd' wakeups if multiple threads are waiting for differing token amounts?
- If this bucket needed to be distributed across multiple independent service instances, how would you maintain atomicity and fair ordering with Redis or Memcached?
- How would you adapt this class to support a burst-size multiplier or prioritization (e.g., critical audit logs vs. routine syncs)?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.