Scope and closures: variables that are not the variable you meant
Every callback reports the last value, a counter never increments, UnboundLocalError on a variable you can see.
What are scope and closure bugs?
The name you wrote resolves to a different variable than the one you meant — a shadowed copy, a shared binding, or one that does not exist yet at the moment the line runs.
Why it happens
Closures capture variables, not values, so callbacks created in a loop can share one binding and all see the final value. Assigning to a name anywhere in a Python function makes it local for the whole function, even before the assignment. Re-declaring a name inside a callback shadows the outer one, so updates land on the copy. And in JavaScript `this` is decided by how a function is called, not where it was written.
How to recognise it
- Every item in a loop behaves like the last one.
- A counter is visibly incremented but stays at its initial value.
- Python reports a variable as local when it is clearly defined above.
- A method works when called normally and breaks when passed as a callback.
Error guides caused by this pattern
All error guides →In-depth guides for common exceptions and runtime errors triggered by scope and closure bugs, with working fixes and real-world examples:
How to fix it
Do not re-declare a name you meant to update from an enclosing scope. Capture per-iteration values explicitly — a default argument in Python, let rather than var in JavaScript. Keep a method attached to its object when passing it somewhere. Declare before use, always.
Breaks vs Works Example
Breaks
# Python: closures capture variable binding, not iteration value
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]Works
# Python: default argument binds variable at function definition
funcs = [lambda x=i: x for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]💡 Closures capture bindings. In JS, use 'let' instead of 'var' so each loop iteration gets a fresh binding.
Practise scope and closure bugs
Working code with one bug in it. Find it, fix it in the browser, and see the explanation. No account needed.
Stuck on your own code?
Paste it into the visualizer and watch it run line by line, with every variable at every step. Free, and it runs in your browser.