Mastery
Mastery/Python/C. Functions & closures
T1 · high-leverage

Late-binding closures: the classic loop-variable-capture bug

A closure captures the variable, not its value at closure-creation time. Since Python's for loop reuses the same variable name on every iteration, every lambda created inside the loop shares that one variable — and by the time any of them actually runs, the loop has already finished and the variable holds its final value. This bites nearly everyone at least once, especially building lists of callbacks.

python
funcs = [lambda: i for i in range(3)]
print("naive:", [f() for f in funcs], " <- all see the FINAL value of i (2), not 0,1,2")

funcs_fixed = [lambda i=i: i for i in range(3)]
print("fixed via default-arg capture:", [f() for f in funcs_fixed])

def make_fn(i):
    return lambda: i
funcs_fixed2 = [make_fn(i) for i in range(3)]
print("fixed via factory function:  ", [f() for f in funcs_fixed2])

Interview angle

The "list of lambdas in a loop" bug is one of the most-asked Python gotcha questions, because it's subtle, genuinely common, and has a clean, testable fix — which makes it a great springboard into a deeper conversation about closures and variable scoping rather than a pure trivia check.

In the industry

This shows up realistically in GUI/event-handler registration code (binding callbacks inside a loop) and in dynamically generated test cases — both real, documented bug classes with canonical Stack Overflow answers. Modern linters catch it too: ruff's B023 specifically flags "function defined in a loop uses a loop variable," turning what used to be a purely experiential lesson into something CI can catch before review.