Closures capture variables by reference, so they see the final loop value
In Python, a closure captures the variable itself, not the value it holds at the time the closure is created. When you define a function inside a loop that references the loop variable, all the functions share the same cell for that variable. After the loop finishes, the variable holds its last value, so every closure sees that final value. This is called late binding. To capture the current value, you must bind it explicitly, for example by using a default argument (lambda x=i: x) or by wrapping the closure in a factory function that takes the value as a parameter. This is a common source of bugs in code that creates callbacks, lambdas, or functions inside loops.
Closures capture variables, not values. The variable is a cell shared by all closures created in the same scope.
Using a default argument (def f(x=i): ...) binds the current value at definition time.
A factory function (def make(i): def f(): return i; return f) creates a new scope per iteration.
functools.partial can also bind values at creation time.
Common mistake: assuming each lambda in a loop captures a different value. They all capture the same variable.
Common mistake: using a loop variable in a callback and being surprised it uses the final value when called later.
Version note: this behavior is consistent across Python 3 and is a direct consequence of how closures and cells work.
0-2 years experience
2-5 years experience
5-8 years experience
8+ years experience