Time to complete: 30-60 minutes
Difficulty: Intermediate
Skills tested: Application Security, Algorithm Design, Edge Case Handling
A Cautionary Tale
In April 2020, security researcher Tom Anthony discovered he could crack into any password-protected Zoom meeting in under 3 minutes. The flaw? No rate limiting on password attempts combined with Zoom's default 6-digit numeric passwords meant attackers could brute-force all 1 million possible combinations within minutes using basic Python code and a handful of cloud servers. Zoom immediately took down their web client on April 2nd to fix the vulnerability, but the damage was doneβduring peak pandemic lockdown, when millions relied on Zoom for private business meetings, therapy sessions, and confidential legal consultations. Want to make sure your rate limiter doesn't have the same flaw? Let's find out.
The Challenge
You're tasked with implementing a rate limiter - the same defense mechanism that protects Twitter, GitHub, and Stripe from API abuse. Sounds simple? Let's see if your implementation can pass 30 comprehensive tests covering edge cases, boundary conditions, and security vulnerabilities.
Why This Matters in Real Life
Every API You've Used Has Rate Limiting
Ever seen this error?
HTTP 429: Too Many Requests
{
"error": "Rate limit exceeded",
"retry_after": 38
}
That's rate limiting in action. Here's what's actually happening behind the scenes:
Twitter/X API
- Limit: 900 requests per 15 minutes (standard users)
- Verified accounts: 10,000 tweets/day
- Why: Prevent spam, monetize premium tiers, maintain stability
- Impact if broken: Platform instability, bot takeover, service degradation
GitHub API
- Unauthenticated: 60 requests/hour
- Authenticated: 5,000 requests/hour
- Enterprise: 15,000 requests/hour
- Why: Prevent abuse, ensure API availability for legitimate developers
- Impact if broken: API unavailability, resource exhaustion attacks
Stripe Payment API
- Default: 25 requests/second per endpoint
- Payment Intents: 1,000 updates per hour
- Why: Protect payment infrastructure, prevent race conditions, reserve capacity for critical transactions
- Impact if broken: Payment fraud, financial losses, compliance violations
The Security Implications
Rate limiting isn't just about preventing overuse - it's a critical security control.
π What Happens When Rate Limiting Fails?
1. Brute Force Attacks
# Without rate limiting, attackers can try 1000s of passwords per second
for password in password_list:
response = login(username, password)
if response.status == 200:
print(f"Password found: {password}")
2. Credential Stuffing
# Attackers test millions of leaked username/password pairs
for username, password in leaked_credentials:
if try_login(username, password):
compromise_account(username)
3. API Abuse & Resource Exhaustion
# Single attacker can consume all your API capacity
while True:
for endpoint in expensive_endpoints:
requests.get(endpoint) # Costs you $$$ per call
4. Distributed Denial of Service (DDoS)
# Coordinated attack from multiple IPs
# Without per-user rate limiting, service goes down
botnet.attack(target_api)
The Challenge: Sliding Window Rate Limiter
The Problem
You need to implement this function:
from typing import List, Tuple
def check_rate_limit(
request_times: List[float], # Timestamps of previous requests
current_time: float, # Current request timestamp
max_requests: int # Max requests per 60 seconds
) -> Tuple[bool, float]: # (allowed?, retry_after_seconds)
"""
Implement a 60-second sliding window rate limiter.
Returns:
(True, 0.0) if request allowed
(False, N) if rate limited, retry after N seconds
"""
# YOUR CODE HERE
pass
Real-World Example
# Simulating Twitter's rate limiting
request_times = [100.0, 110.0, 120.0, 121.0, 121.5] # Previous requests
current_time = 122.0 # New request arrives
max_requests = 5 # Limit: 5 per minute
result = check_rate_limit(request_times, current_time, max_requests)
# Expected: (False, 38.0) β Rate limited! Wait 38 seconds
Why This Is Harder Than It Looks
Edge Case 1: Old Requests Should Be Ignored
# Requests outside the 60-second window shouldn't count
request_times = [1.0, 2.0, 3.0, 60.0, 61.0, 62.0, 63.0, 64.0]
current_time = 120.0
# Only [60.0, 61.0, 62.0, 63.0, 64.0] count
# [1.0, 2.0, 3.0] are >60 seconds old
Edge Case 2: The 60-Second Boundary Bug π
Security vulnerability: Many implementations get this wrong!
# VULNERABLE CODE (using >):
recent = [t for t in request_times if t > window_start]
# SECURE CODE (using >=):
recent = [t for t in request_times if t >= window_start]
Why it matters:
Using > instead of >= allows attackers to bypass the rate limit at the exact 60-second boundary. Over 1 year, this allows:
- GitHub API: 8,760 extra unauthorized requests
- Stripe API: 86,400 extra unauthorized payment attempts per day
Edge Case 3: Variable Rate Limits
Your code must work with ANY max_requests value:
# Strict API (1 request/minute)
check_rate_limit([119.5], 120.0, 1) β (False, 59.5)
# Typical API (5 requests/minute)
check_rate_limit([100, 110, 120], 121.0, 5) β (True, 0.0)
# High-volume API (100 requests/minute)
check_rate_limit([100, 110, 120], 121.0, 100) β (True, 0.0)
Never hardcode max_requests=5 in your implementation!
The Testing Gauntlet
Your implementation will face 30 comprehensive tests:
β Basic Functionality (Tests 1-5)
- Empty request history
- Single request
- Under limit scenarios
- At limit scenarios
- All requests old (>60 seconds)
π― Boundary Conditions (Tests 6-10)
- Exactly at 60-second boundary
- Just inside/outside window
- Mixed old and new requests
- Edge case timing
β±οΈ Timing Scenarios (Tests 11-15)
- Very recent bursts
- Spread across full window
- Gradual spacing patterns
- One request at boundary
π§ Variable Limits (Tests 16-20)
- Strict limits (max=1, 2, 3)
- Typical limits (max=5)
- Lenient limits (max=10)
- High volume (max=100)
π¬ Fractional Seconds (Tests 21-25)
- Fractional timestamps
- Fractional retry_after
- Microsecond precision
- Precise boundaries
β‘ Edge Cases (Tests 26-30)
- Complex mixed scenarios
- Same timestamp requests
- High volume at limit
- Boundary with old requests
The Exercise
What You'll Get
-
LeetCode-style test file (
rate_limiter_30_tests.py)- Implement your solution in a designated section
- Run the file to see results instantly
- Beautiful colored output showing pass/fail
- 30 comprehensive test cases
-
Detailed failure reports
- See exactly what went wrong
- Compare expected vs actual output
- Debug with confidence
-
Progressive difficulty
- Basic functionality tests first
- Then boundary conditions
- Then edge cases
- Build confidence as you go
Sample Output
$ python3 rate_limiter_30_tests.py
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β RATE LIMITER CHALLENGE β
β 30 TEST CASES β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
PASS - Test 1: Under limit (3/5 requests)
β
PASS - Test 2: At limit (5/5 requests within window)
β
PASS - Test 3: Empty request history
...
β
PASS - Test 28: Single recent request at boundary
β
PASS - Test 29: All requests at same timestamp
β
PASS - Test 30: High volume at limit
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUMMARY
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Tests Passed: 30/30
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π PERFECT! ALL 30 TESTS PASSED! π β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why This Exercise Builds Real AppSec Skills
1. Security Boundary Conditions
Rate limiting is all about boundaries. Get them wrong, and you have a security vulnerability.
-
>=vs>(60-second boundary) - Off-by-one errors
- Floating-point precision
2. Defensive Programming
- Handle empty lists
- Handle single elements
- Handle extreme values (max=1, max=100)
- Never assume inputs are "reasonable"
3. Algorithm Correctness
- Sliding window vs fixed window
- Time complexity: O(n) filtering
- Space complexity: O(1) calculation
4. Real-World API Design
- Return meaningful error codes
- Provide
retry_afterguidance to clients - Make limits configurable (not hardcoded)
5. Comprehensive Testing
- Edge cases (empty, boundary, extreme)
- Fractional seconds precision
- Variable limits (1 to 100)
- Complex mixed scenarios
Common Mistakes to Avoid
β Mistake #1: Hardcoding the Limit
# BAD - Only works for max_requests=5
if len(recent_requests) < 5:
return (True, 0.0)
# GOOD - Works for any limit
if len(recent_requests) < max_requests:
return (True, 0.0)
β Mistake #2: Wrong Boundary Check
# VULNERABLE - Bypass at 60-second boundary
recent = [t for t in request_times if t > window_start]
# SECURE - Correct boundary handling
recent = [t for t in request_times if t >= window_start]
β Mistake #3: Returning Wrong Type
# WRONG - Returns only bool
def check_rate_limit(...):
return True # Missing retry_after!
# CORRECT - Returns tuple
def check_rate_limit(...) -> Tuple[bool, float]:
return (True, 0.0) # Both values
β Mistake #4: Integer vs Float
# LESS PRECISE - Loses fractional seconds
retry_after = int((oldest + 60.0) - current_time)
return (False, retry_after) # 38 instead of 38.5
# MORE PRECISE - Keeps fractional seconds
retry_after = (oldest + 60.0) - current_time
return (False, retry_after) # 38.5
Take the Challenge
Get the Exercise Files
Option 1:
Just visit (and Star!) my GitHub repo
Option 2:
# Clone or download the exercise files
git clone https://github.com/fosres/AppSec-Exercises.git
cd AppSec-Exercises/api_security/api_request_limiter/challenge/
# You will see the [Python challenge
# file](https://github.com/fosres/AppSec-Exercises/blob/main/api_security/api_request_limiter/challenge/rate_limiter_30_tests.py):
# Edit `rate_limiter_30_tests.py`
# Run the tests
python3 rate_limiter_30_tests.py
Files included:
-
rate_limiter_30_tests.py- Main test file (30 tests) -
solution_example.py- Minimal example (for after completion) -
README.md- Complete instructions
Time Yourself
- β±οΈ 30 minutes: Good pace, you know your stuff
- β±οΈ 60 minutes: Normal, especially if you're learning
- β±οΈ 90+ minutes: Take your time, debug carefully
Share Your Results
When you pass all 30 tests:
# Share on Twitter/X
Just passed 30/30 tests on the Rate Limiter AppSec Challenge!
π― 30 comprehensive tests
π Production-ready implementation
πͺ Security-focused!
#AppSec #Python #100DaysOfCode
What You'll Learn
By completing this challenge, you'll understand:
β
Why rate limiting is critical for API security
β
How to implement sliding window algorithms correctly
β
Security boundary conditions that attackers exploit
β
Comprehensive testing approaches with edge cases
β
Production-grade code vs quick prototypes
For Hiring Managers
This exercise tests candidates on:
- β Algorithm correctness
- β Edge case handling
- β Security awareness
- β Code quality
- β Testing thoroughness
If a candidate passes all 30 tests, they demonstrate:
- Understanding of real-world security controls
- Ability to handle boundary conditions
- Attention to detail in implementation
- Experience with comprehensive testing
Level Up: After You Pass
1. Optimize Your Solution
Can you reduce your code from 70 lines to 10 lines?
# Minimal solution using list comprehension
def check_rate_limit(request_times, current_time, max_requests):
window_start = current_time - 60.0
recent = [t for t in request_times if t >= window_start]
if len(recent) < max_requests:
return (True, 0.0)
retry_after = (recent[0] + 60.0) - current_time
return (False, max(0.0, retry_after))
2. Add More Features
- Multiple time windows (1 min, 1 hour, 1 day)
- Per-user tracking with Redis
- Distributed rate limiting across servers
- Token bucket algorithm
3. Build a Real API
from fastapi import FastAPI, HTTPException
from typing import Dict
import time
app = FastAPI()
user_requests: Dict[str, List[float]] = {}
@app.get("/api/resource")
async def protected_endpoint(user_id: str):
current_time = time.time()
requests = user_requests.get(user_id, [])
allowed, retry_after = check_rate_limit(requests, current_time, 10)
if not allowed:
raise HTTPException(
status_code=429,
headers={"Retry-After": str(int(retry_after))},
detail="Rate limit exceeded"
)
# Record this request
user_requests[user_id] = requests + [current_time]
return {"message": "Success!"}
4. Write About It
Share your experience:
- What edge cases surprised you?
- How did you debug failures?
- What did you learn about security?
Resources
Recommended Reading
- π "API Security in Action" by Neil Madden (Chapter 3, pp. 67-69)
- π "Hacking APIs" by Corey Ball (Chapter 13, pp. 276-280)
- π "Secure by Design" by Johnsson, Deogun, and Sawano
Real-World Examples
Ready to Start?
Download the exercise and prove your AppSec skills:
Good luck! And remember - if your solution passes all 30 tests, you've built something production-ready. π
Discussion
- What was your biggest challenge in this exercise?
- Did you discover any edge cases we didn't test?
- How would you extend this to handle distributed systems?
- Share your solution approach in the comments!
This exercise is part of a series on practical AppSec skills. Follow for more hands-on security challenges!
If you like this exercise please leave a star on my GitHub Repo!
You can see my solution here
You can see Claude Code's solution here
Top comments (0)