Inspect GitHub's rate-limit headers (works unauthenticated, lower limit)
Greetings, brave adventurer! An open gate with no guard is soon overrun. Rate limiting is the discipline that protects an API from being hammered into the ground - whether by a runaway script, a thundering crowd, or a deliberate attacker. This quest, Rate Limiting, teaches you to throttle fairly: to slow callers down without locking honest users out.
You will master the token bucket and leaky bucket algorithms, learn to return a polite 429 Too Many Requests with the headers that tell clients exactly when to come back, distinguish a per-second rate from a monthly quota, and build a client that backs off gracefully when throttled.
๐ The Legend Behind This Quest¶
Every shared service faces the same threat: one greedy caller can consume the resources meant for everyone. Without limits, a single buggy loop sending thousands of requests a second can exhaust a database, spike a cloud bill, and take an API down for all its users. Rate limiting is the moat and the gatekeeper combined.
Done well, it is invisible to good citizens and a firm, fair wall to abusers. Done badly, it punishes legitimate users with cryptic failures. This quest teaches the algorithms and the etiquette - the headers, the codes, the backoff - that make limiting both effective and humane.
๐ฏ Quest Objectives¶
By the time you complete this journey, you will have mastered:
Primary Objectives (Required for Quest Completion)¶
- Why Rate Limit - Explain the abuse, fairness, and cost problems it solves
- Token Bucket - Implement the most common limiting algorithm
- Leaky Bucket - Contrast it with token bucket and know when to use each
- The 429 Response - Return
429 Too Many Requestswith the right headers - Client-Side Throttling - Read rate-limit headers and back off correctly
Secondary Objectives (Bonus Achievements)¶
- Rate vs Quota - Distinguish a short-term rate from a long-term quota
- Fixed vs Sliding Window - Compare simpler counting strategies
- Distributed Limiting - Understand why limiters often live in a shared store like Redis
Mastery Indicators¶
You'll know you've truly mastered this quest when you can:
- [ ] Implement a token bucket from scratch in a few lines of code
- [ ] Explain the burst behavior difference between token and leaky buckets
- [ ] Read X-RateLimit-* and Retry-After headers and act on them
- [ ] Choose an algorithm for a given traffic pattern and justify it
๐บ๏ธ Quest Prerequisites¶
๐ Knowledge Requirements¶
- You have completed API Fundamentals or know status codes and headers
- You recognize the
429status code - You can make requests with curl
๐ ๏ธ System Requirements¶
- Modern operating system (Windows 10+, macOS 10.14+, or Linux)
-
curlavailable in your terminal - Optional: Python 3 for the token-bucket lab
- An internet connection
๐ง Skill Level Indicators¶
This ๐ก Medium quest expects: - [ ] You can read response headers - [ ] You can implement a small algorithm from a description - [ ] Ready for 75-90 minutes of focused learning
๐ Choose Your Adventure Platform¶
Rate limiting concepts are universal. These setups let you observe real rate-limit headers and run the local lab.
๐ macOS Kingdom Path¶
Click to expand macOS instructions
๐ช Windows Empire Path¶
Click to expand Windows instructions
๐ง Linux Territory Path¶
Click to expand Linux instructions
โ๏ธ Cloud Realms Path¶
Click to expand Cloud/Container instructions
๐งโโ๏ธ Chapter 1: Why Limit, and the Token Bucket¶
A rate limit caps how many requests a caller may make in a window of time. The most widely used algorithm is the token bucket because it allows short bursts while enforcing a steady long-term rate.
โ๏ธ Skills You'll Forge in This Chapter¶
- The reasons to rate limit
- How the token bucket works
- Implementing one in code
๐๏ธ The Token Bucket Algorithm¶
Picture a bucket that holds up to capacity tokens and refills at rate tokens per second. Each request must take one token; if the bucket is empty, the request is rejected. This permits a burst up to capacity, then settles to the steady refill rate.
import time
class TokenBucket:
def __init__(self, capacity, refill_per_second):
self.capacity = capacity # max burst size
self.rate = refill_per_second # steady-state allowance
self.tokens = capacity # start full
self.last = time.monotonic()
def allow(self):
now = time.monotonic()
# Add tokens for the time elapsed, capped at capacity
self.tokens = min(self.capacity, self.tokens + (now - self.last) * self.rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True # request permitted
return False # bucket empty -> reject with 429
# Allow 5 quick requests (burst), then 2 per second sustained
bucket = TokenBucket(capacity=5, refill_per_second=2)
print([bucket.allow() for _ in range(7)]) # first 5 True, then False, False
The first five requests drain the burst capacity; the sixth and seventh are rejected until tokens refill. This is why token buckets feel forgiving: a user clicking quickly is fine, a script in a tight loop is not.
๐ Knowledge Check: Token Bucket¶
- What does
capacitycontrol versusrefill_per_second? - Why does a token bucket allow short bursts?
- What should the server return when
allow()isFalse?
โก Quick Wins and Checkpoints¶
- Ran the bucket: You saw burst requests pass and excess requests rejected
- Read real headers: You inspected a live API's rate-limit headers
๐งโโ๏ธ Chapter 2: Leaky Bucket, the 429, and Headers¶
The leaky bucket is the token bucket's sibling. The server-side etiquette - the 429 code and the headers that accompany it - is what makes limiting usable by clients.
โ๏ธ Skills You'll Forge in This Chapter¶
- Leaky bucket versus token bucket
- Returning a correct
429 - The rate-limit headers clients rely on
๐๏ธ Leaky Bucket vs Token Bucket¶
A leaky bucket queues incoming requests and processes them at a fixed "leak" rate; if the queue overflows, new requests are dropped. It smooths bursts into a constant output stream. The contrast:
| Token bucket | Leaky bucket | |
|---|---|---|
| Allows bursts | Yes, up to capacity | No, output is smoothed |
| Output rate | Variable up to a cap | Constant |
| Best for | APIs that tolerate spikes | Traffic shaping, steady throughput |
When a limit is exceeded, return 429 Too Many Requests with headers that tell the client how to behave:
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Retry-After: 30
{ "type": "https://api.example.com/errors/rate-limit",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry in 30 seconds." }
RateLimit-Limit is the ceiling, RateLimit-Remaining is what is left in this window, RateLimit-Reset is seconds until the window resets, and Retry-After is the explicit instruction. Inspect a real API's headers:
# GitHub returns X-RateLimit-* headers on every response
curl -s -D - -o /dev/null https://api.github.com/users/octocat \
| grep -i 'x-ratelimit'
๐ Knowledge Check: 429 & Headers¶
- Which algorithm produces a perfectly constant output rate?
- What does
RateLimit-Remaining: 0tell the client? - What is the difference between
RateLimit-ResetandRetry-After?
๐งโโ๏ธ Chapter 3: Rate vs Quota and Client Throttling¶
A rate limits short-term bursts (per second or minute); a quota limits long-term consumption (per day or month). A good client reads the headers and throttles itself before it ever gets a 429.
โ๏ธ Skills You'll Forge in This Chapter¶
- Distinguishing rates from quotas
- Building a self-throttling client
- Backing off on 429
๐๏ธ Rate vs Quota¶
A typical API enforces both: "60 requests per minute (rate) and 5,000 per day (quota)." The rate stops bursts; the quota stops a slow drip from exhausting a billing tier. A free plan might cap the quota low while a paid plan raises it.
A well-behaved client reads the remaining-count headers and slows down proactively, and on a 429 it waits the advised time before retrying:
import time, requests
def polite_get(url):
while True:
resp = requests.get(url, timeout=5)
if resp.status_code == 429:
# Server told us how long to wait - honor it exactly
wait = float(resp.headers.get("Retry-After", "1"))
time.sleep(wait)
continue
# If we are nearly out of budget, pause to avoid hitting the limit
remaining = int(resp.headers.get("RateLimit-Remaining", "1"))
if remaining == 0:
reset = float(resp.headers.get("RateLimit-Reset", "1"))
time.sleep(reset)
return resp
In production, limiters usually live in a shared store like Redis so that every server in a fleet enforces the same per-client limit - otherwise a client could multiply its allowance by spreading requests across servers.
๐ Knowledge Check: Rate vs Quota¶
- Give an example of a rate and a quota for the same API
- Why should a client throttle itself instead of waiting for 429s?
- Why do distributed limiters need a shared store?
๐ฎ Mastery Challenges¶
๐ข Novice Challenge: Read the Headers¶
Objective: Inspect a real API's rate-limit headers.
Requirements:
- [ ] Call https://api.github.com/rate_limit and read your remaining count
- [ ] Identify the limit, remaining, and reset values
- [ ] State what would happen if remaining reached zero
Validation: You can report all three values and the consequence.
๐ก Intermediate Challenge: Build a Limiter¶
Objective: Implement and test a token bucket.
Requirements:
- [ ] Build a TokenBucket that allows a burst of 5 and refills at 1/second
- [ ] Demonstrate that a burst passes and the next request is rejected
- [ ] Show that waiting one second lets a new request through
Validation: Your bucket rejects exactly the right requests.
๐ด Advanced Challenge: Polite Client¶
Objective: Write a self-throttling client.
Requirements:
- [ ] Honor Retry-After on a 429
- [ ] Pause proactively when RateLimit-Remaining hits zero
- [ ] Never exceed the advertised limit across a run of requests
Validation: A burst of calls completes without a single un-handled 429.
๐ Quest Rewards & Achievements¶
๐๏ธ Badges Earned: - ๐ Warden of the Throttle - You implemented a working rate limiter - ๐ชฃ Bearer of the Bucket - You mastered the token and leaky bucket algorithms
๐ ๏ธ Skills Unlocked: - Token Bucket Implementation - Enforce a fair, bursty rate - Client-Side Throttling - Respect limits before they bite
๐ Unlocked Quests: - API Versioning - Evolve limits and contracts safely - API Documentation - Publish your limits so clients can plan
๐ Progression Points: +60 XP
๐บ๏ธ Next Steps in Your Journey¶
Continue the Main Story: - ๐ฏ API Versioning - Evolve without breaking clients
Explore Side Adventures: - โ๏ธ Error Handling - The 429 in context - โ๏ธ API Documentation - Document your limits
Character Class Recommendations¶
๐ป Software Developer: Continue to API Versioning ๐๏ธ System Engineer: Explore Error Handling ๐ก๏ธ Security Specialist: Check out API Authentication
๐ Resources¶
Official Documentation¶
- RFC 6585: Additional HTTP Status Codes (429) - Defines
429 Too Many Requests - IETF RateLimit header fields draft - Standardizing the headers
- MDN: 429 Too Many Requests - The status code reference
Community Resources¶
- GitHub: Rate limits for the REST API - A real implementation
- Stripe: Rate limits - Token bucket in production
- Cloudflare: What is rate limiting? - A clear conceptual overview
Learning Materials¶
- Token bucket (Wikipedia) - The algorithm formally
- Leaky bucket (Wikipedia) - The smoothing counterpart
- Redis: Rate limiting patterns - Distributed limiting
๐ค Quest Completion Checklist¶
- โ Completed all primary objectives
- โ Implemented a working token bucket
- โ Answered all knowledge check questions
- โ Completed at least one mastery challenge
- โ Explored the resource library
- โ Identified your next quest in the journey
๐ธ๏ธ Knowledge Graph¶
Structured wiki-links connect this quest to the IT-Journey knowledge graph. Open the Obsidian Graph View to explore connections.
Level hub: [[Level 0111 - API Development]] Overworld: [[๐ฐ Overworld - Master Quest Map]] Prerequisites: [[API Fundamentals: HTTP, Requests, and JSON]] Unlocks: [[API Versioning: URI, Headers, and Backward Compatibility]] ยท [[API Documentation: OpenAPI, Swagger, and Contract-First]] Obsidian docs: [[Obsidian Knowledge Graph and Wiki Links]]