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.