Exercises - closures
Basic Closures
Section titled “Basic Closures”Exercise 1 — 🟢 Beginner
Create a closure that generates a greeting function for a specific language.
# expected:# greet_en = make_greeter("Hello")# greet_es = make_greeter("Hola")# greet_fr = make_greeter("Bonjour")
# greet_en("Alice") → "Hello, Alice!"# greet_es("Alice") → "Hola, Alice!"# greet_fr("Alice") → "Bonjour, Alice!"Exercise 2 — 🟢 Beginner
Create a closure that keeps track of how many times a function has been called.
# expected:# counter = make_call_counter()# counter() → "Called 1 time"# counter() → "Called 2 times"# counter() → "Called 3 times"Exercise 3 — 🟢 Beginner
Create a closure that applies a fixed discount to any price.
# expected:# ten_percent_off = make_discount(10)# twenty_percent_off = make_discount(20)
# ten_percent_off(100) → 90.0# twenty_percent_off(100) → 80.0# ten_percent_off(50) → 45.0Function Factories
Section titled “Function Factories”Exercise 4 — 🟡 Intermediate
Create a closure that generates a multiplier function for a given factor, then use it with map() to transform a list.
numbers = [1, 2, 3, 4, 5]
# expected:# double = make_multiplier(2)# triple = make_multiplier(3)
# double(5) → 10# list(map(double, numbers)) → [2, 4, 6, 8, 10]# list(map(triple, numbers)) → [3, 6, 9, 12, 15]Exercise 5 — 🟡 Intermediate
Create a closure that generates a power function for a given exponent, then use it with map() and filter().
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# expected:# square = make_power(2)# cube = make_power(3)
# square(4) → 16# list(map(square, numbers)) → [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# list(map(cube, filter(lambda x: x % 2 == 0, numbers))) → [8, 64, 216, 512, 1000]Exercise 6 — 🟡 Intermediate
Create a closure that builds a logger with a fixed prefix and log level.
# expected:# db_info = make_logger("DATABASE", "INFO")# api_error = make_logger("API", "ERROR")
# db_info("Connection established") → "[INFO] [DATABASE] Connection established"# api_error("Timeout after 30s") → "[ERROR] [API] Timeout after 30s"Stateful Closures
Section titled “Stateful Closures”Exercise 7 — 🟡 Intermediate
Create a closure that accumulates values and returns the running total each time it is called.
# expected:# accumulator = make_accumulator()# accumulator(10) → 10# accumulator(20) → 30# accumulator(5) → 35# accumulator(100) → 135Exercise 8 — 🟡 Intermediate
Create a closure that implements a simple stack with push and pop operations, returning both functions.
# expected:# push, pop = make_stack()# push(1)# push(2)# push(3)# pop() → 3# pop() → 2# pop() → 1# pop() → None ← empty stackExercise 9 — 🔴 Advanced
Create a closure that implements a rate limiter — it allows a function to be called at most n times, then raises an error.
# expected:# limited = make_rate_limiter(3)# limited() → "Call 1 of 3"# limited() → "Call 2 of 3"# limited() → "Call 3 of 3"# limited() → ❌ RuntimeError: Rate limit exceeded — max 3 calls allowedLate Binding Gotcha
Section titled “Late Binding Gotcha”Exercise 10 — 🟢 Beginner
Fix this broken code so each function returns its own index value:
# ❌ brokenfuncs = [lambda: i for i in range(5)]print([f() for f in funcs]) # [4, 4, 4, 4, 4]
# ✅ fix it so the output is:# [0, 1, 2, 3, 4]Exercise 11 — 🟡 Intermediate
Fix this broken code that tries to create a list of multiplier functions:
# ❌ brokenmultipliers = [lambda x: x * i for i in range(1, 6)]print([f(10) for f in multipliers]) # [50, 50, 50, 50, 50]
# ✅ fix it so the output is:# [10, 20, 30, 40, 50]Combined Exercises
Section titled “Combined Exercises”Exercise 12 — 🔴 Advanced
Create a closure that memoizes a function manually — without using functools.lru_cache. It should cache results and return them on repeated calls.
# expected:# memoized_square = memoize(lambda x: x ** 2)# memoized_square(4) → 16 (computed)# memoized_square(4) → 16 (from cache)# memoized_square(5) → 25 (computed)# memoized_square(5) → 25 (from cache)Exercise 13 — 🔴 Advanced
Create a closure that composes two functions together — the output of the first becomes the input of the second. Then use it to build a text processing pipeline.
# expected:# strip_and_upper = compose(str.upper, str.strip)# strip_and_upper(" hello ") → "HELLO"
# Build a pipeline:# clean = compose(str.strip, str.lower)# clean(" HELLO WORLD ") → "hello world"Try implementing each solution as a pure closure first — no classes, no functools. The goal is to understand how captured state and returned functions can replace objects entirely.