TweakCode
Worked Systems lessons

Rate Limiter · Stage 1 of 3

Token Bucket Rate Limiting

Refill tokens with elapsed time to allow bursts and cap the average

Foundations

A rate limiter decides, per caller, whether a request may proceed. A token bucket holds up to capacity tokens and refills at rate tokens per second; each request spends one token, and a request that finds the bucket empty is denied. Capacity bounds the burst a caller can send at once, and the refill rate bounds the long-run average.

No timer refills idle buckets. Each check computes the refill lazily: add the elapsed time since the last check times rate, capped at capacity, then try to spend one token. So each caller costs two stored numbers, tokens and the last check time, and one atomic update per request. A denied caller is told how long until the next token arrives.

Take capacity 2 and a refill of 1 token per second. Requests at 0.0 and 0.1 spend both tokens, and one at 0.2 is denied, about 0.8 seconds before a token returns. By 1.5 the bucket has refilled to 1.5 tokens, so a request passes and 0.5 remains.

time (s)

0123
0.0
0.1
0.2
1.5

tokens before

0123
2
1.1
0.2
1.5

decision

0123
allow
allow
deny
allow
Each check refills by elapsed time before spending. The request at 0.2 finds 0.2 tokens and is denied; by 1.5 the bucket holds 1.5, enough for one more.
  • Requirements

    Each worked system starts from a scoped list of verbs and numbers.

    Open lesson
  • Caching

    The read path sits behind cache-aside.

    Open lesson

Case Scenario

The design brief

Limit an API user

Each API key may call your service 100 times a minute. Over the limit, the caller receives 429 and a hint about when to retry.

Input
requests = [0, 0.1, 0.2, 0.3, 1.5, 1.6], capacity = 3, rate = 1
Output
[True, True, True, False, True, False]

To see how a token bucket allows bursts but caps the average, consider per-key API limits like GitHub's or Stripe's: each API key may call the service 100 times a minute, and an over-limit caller receives 429 with a hint about when to retry. One atomic refill-and-spend in a shared store decides each request.

An API key may be allowed 100 calls per minute, but real callers do not arrive at perfectly even intervals. The service should accept a legitimate short burst, reject sustained excess with a useful retry time, and keep one caller from consuming the expensive handler. Each decision must take one atomic round trip to a shared counter store, never a query to the primary database.

Pro

The rest of this is on Pro

The idea above is free. The walkthrough, the cost, and the practice are on Pro.