What is variable shadowing and why is it problematic?
Variable shadowing occurs when a variable declared in an inner scope has the same name as a variable in an outer (enclosing) scope. When the name is referenced inside the inner scope, the inner binding takes precedence, effectively hiding ('shadowing') the outer one. The outer variable still exists and retains its value, but it is inaccessible by that name within the shadowing scope.
In Python, scopes are resolved by the LEGB rule: Local → Enclosing → Global → Built-in. When you assign to a name inside a function without `global` or `nonlocal`, Python creates a new local binding. If a global or enclosing variable has the same name, it is shadowed.
Shadowing becomes problematic in several scenarios. **Accidental shadowing**: a function parameter or local variable coincidentally shares a name with a global, causing reads within the function to see the local value—potentially `None` or `0` rather than the intended global. **The UnboundLocalError trap**: if you assign to a name anywhere in a function, Python treats that name as local throughout the entire function body. Reading it before the assignment raises `UnboundLocalError`, even though a global exists. This surprises developers who expect to read the global then rebind locally.
Built-in shadowing is especially dangerous: naming a variable `list`, `id`, `type`, or `input` shadows Python's built-ins for the entire module scope, breaking all subsequent uses of those built-ins in that module.
Shadowing also manifests in loop variables. A loop variable like `i` or `item` leaks into the enclosing scope in Python (unlike many other languages), so using that name after the loop gives the last iteration's value, not what the name meant before the loop.
Why it's problematic: it makes code harder to reason about because the same name means different things in different parts of the code. It causes subtle bugs that are hard to detect because the code is syntactically valid. Linters like `pylint` and `flake8` flag shadowing with warnings like `W0621 (redefined-outer-name)` for exactly this reason.
Best practices: prefer distinct names, use `global`/`nonlocal` explicitly when mutation of outer scope is intended, and enable linter warnings for redefined names.
Explains LEGB rule and how inner assignment hides outer binding, demonstrates the UnboundLocalError trap, gives a concrete example of accidental shadowing.
Explains why Python makes the whole function local when any assignment exists for a name, covers built-in shadowing risk, mentions loop variable leakage in Python, and explains why linters flag it.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment