Skip to content

Exercises - 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.0

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"

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) β†’ 135

Exercise 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 stack

Exercise 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 allowed

Exercise 10 β€” 🟒 Beginner Fix this broken code so each function returns its own index value:

# ❌ broken
funcs = [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:

# ❌ broken
multipliers = [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]

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.