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 is scope and closure bug?
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.
Errors and symptoms this causes
- UnboundLocalError: cannot access local variable
- ReferenceError: Cannot access before initialization
- all callbacks use the last value
- counter stays at zero
- this is undefined
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.
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.