"Python Closures Late Binding: Why Loops Break Them"
"Python closures late binding means loop-created functions all see the loop variable's final value — five lambdas, five 4s. This guide shows the exact broken output, the cell-variable root cause, the one-line default-argument fix, the factory-function alternative, and how to verify each."
funcs = [lambda: i for i in range(5)] print([f() for f in funcs]) Five functions, built from i = 0 through i = 4 . What prints? If you said [0, 1, 2, 3, 4] , you're wrong — and you're in excellent company, because this exact snippet has fooled senior engineers in interviews for twenty years. The real output is [4, 4, 4, 4, 4] . The culprit is python closures late binding: a closure captures the *variable*, not the value, and looks it up when the function is *called* — long after the loop finished with i stuck at 4. What the error looks like The classic list-comprehension version: funcs = [lambda: i for i in range(5)] print([f() for f in funcs]) # Actual output: [4, 4, 4, 4, 4] # Expected: [0, 1, 2, 3, 4] The multiplier version that shows up in real code (per-item pricing, per-row validators): multipliers = [lambda x: x * i for i in range(1, 4)] print([m(10) for m in multipliers]) # Actual output: [30, 30, 30] # Expected: [10, 20, 30] And this is not a lambda quirk — plain def inside a loop does the same thing, and so do callbacks registered in a loop (button handlers, event subscribers, scheduled jobs). Anywhere you create functions in a loop and call them later, late binding is waiting. Why it happens: python closures late binding in plain terms When an inner function references a name from its enclosing scope, Python doesn't copy the value into the function. It creates a cell object — a small box holding a reference to the *variable* — and every closure created in that scope shares the same box. The lookup happens at call time , not at definition time. So in [lambda: i for i in range(5)] , all five lambdas close over the *same* cell for i . The loop runs to completion, i ends at 4, and only then do you call the functions — each one opens the shared box…