Python Closures & Scope
Apply your skills with a real-world coding challenge. Try to solve it yourself first!
Coding Challenge: Secure API Rate Limiter
Difficulty Level
Intermediate
Problem Description
Imagine you're basically a backend developer working in a high-traffic web application. To protect your servers from being overwhelmed, you need to implement a secure API rate limiter.
Instead of building the massive Object-Oriented class with __init__ and self towards just a single behavior, you want lightweight and highly secure solution. You decide for use a Python Closure to create a function carrying an invisible "backpack." This rate limiter must securely track and remember how tons of API calls a user is simply allowed to make. Because the tracking data is trapped inside enclosing scope it guarantees complete data privacy and prevents other developers from accidentally modifying it from a global scope, while
your task is to write a parent function that accepts a maximum request limit and returns a child function. The child function will process requests, decrement allowed request count using the nonlocal keyword, and dynamically block access once the limit hits zero.
Input & Output Specifications
- Input:
max_requests(integer): Passed into the parent functioncreate_rate_limiter(max_requests)to set up the closure.- The child function
make_request()takes no arguments. - Output:
- If user has requests remaining, the child function must subtract
1from the total and return the string:"Request processed. [X] requests remaining." - If the user has exhausted their requests, the child function must return a string:
"Error: Rate limit exceeded."
Starter Code Boilerplate
def create_rate_limiter(max_requests):
# TODO: Initialize your tracking variable here in the enclosing scope
def make_request():
# TODO: Use the appropriate keyword to inform Python's LEGB scope
# resolution that you want to modify the enclosing variable
# TODO: Check if requests are still allowed
# TODO: If allowed, decrement the limit and return the success string
# TODO: If not allowed, return the error string
pass
# TODO: Return the child function to create the closure
return
Hints
- The LEGB Rule: When resolving variable names, Python checks Local then Enclosing, then Global, then Built-in scopes. Your child function will need to access a variable residing in the Enclosing scope.
- The
nonlocalSuperpower: If you simply try to writerequests = requests - 1inside your localmake_requestfunction Python will panic and throw an error because it thinks you're basically referencing unassigned local variable. You must explicitly declare the variable using thenonlocalkeyword to tell Python to modify data saved in closure's backpack. - Lightweight vs OOP: Notice how this pattern provides data privacy without needing an entire class structure, and just be careful not to trap massive objects in closures, as the garbage collector cannot clean them up and it could cause memory leaks!
Test Cases
Test Case 1: Standard Usage & Limit Enforcement
* Input:
python
user_api = create_rate_limiter(2)
print(user_api())
print(user_api())
print(user_api())
* Expected Output:
text
Request processed. 1 requests remaining.
Request processed. 0 requests remaining, and
error: Rate limit exceeded.
Test Case 2: Independent Closure Backpacks * Input: ```python premium_user = create_rate_limiter(5) free_user = create_rate_limiter(1)
print(free_user())
print(free_user())
print(premium_user())
* **Expected Output:**text
Request processed. 0 requests remaining.
Error: Rate limit exceeded.
Request processed. 4 requests remaining.
```
(This proves that each closure maintains its own completely isolated enclosing scope!)
Verify Your Solution
Write your solution in the compiler, run it to verify output, then click below to verify.