What is the output of this code and why 🤔?
def make_counters():
counters = []
for i in range(3):
def counter():
return i
counters.append(counter)
return counters
a, b, c = make_counters()
print(a(), b(), c())
Outputs:
a. 0 1 2
b. 2 2 2
c. 0 0 0
d. 1 1 1
e. Raises an error
Why this quiz matters
This short snippet looks simple, but it probes a set of Python behaviors that commonly trip up learners and even experienced developers when writing higher-order functions or factories. The quiz focuses on three tightly related concepts:
1. Closures and capturing variables
A closure is a function that remembers the environment in which it was defined. When you create a function inside another function, that inner function can reference variables from the outer scope. But how those variables are referenced (and when they’re looked up) is crucial.
2. Late binding vs early binding
Python’s name lookup for variables captured by closures happens at call time (often referred to as “late binding”), not necessarily at the moment the inner function is defined. That timing affects what value the inner function returns when you call it later.
3. Loop variables & function factories
Creating functions inside loops is a common pattern (e.g., creating a list of callbacks). When the inner function references the loop variable, you need to understand whether each function captures a distinct value or the same variable that later changes.
How to approach this puzzle (without spoiling it)
Read it slowly: identify where functions are defined and where variables are created/modified.
Ask “when is i evaluated?” — at definition time or at call time?
Consider running a mental simulation: imagine the loop running and the functions being appended — then imagine calling them later.
Try small experiments if you’re stuck: insert a print inside the loop, or inspect attributes of the returned functions in an interactive REPL. (Tip: stepping through with a debugger or using dis can also reveal evaluation order.)
Learning takeaway
Understanding closures and the timing of variable lookups will save you from subtle bugs when building function factories, decorators, callbacks, or any code that generates functions dynamically. Once you can reason about where and when each name is resolved, patterns like these become powerful tools instead of traps.
Write your solution below in the comment or view it here: Link
Top comments (0)