← Back to Additional Topics

Shadowing

Additional TopicsMid

The Question

What is variable shadowing and why is it problematic?

What a Strong Answer Covers

  • Inner scope hides outer." "Outer still exists." LEGB connection.

Senior-Level Answer

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.

What Separates a 2/3 from a 3/3

2/3 — Passing but Incomplete

Explains LEGB rule and how inner assignment hides outer binding, demonstrates the UnboundLocalError trap, gives a concrete example of accidental shadowing.

3/3 — Strong Answer

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.

Common Mistakes

  • Confusing shadowing with mutation — shadowing creates a new binding, mutation modifies the same object
  • Not knowing about UnboundLocalError from partial shadowing
  • Forgetting that built-in names (list, id, type) are just names in the builtins scope and can be shadowed
  • Saying shadowing is always a bug — sometimes intentional (test fixtures, context managers) but should be explicit

Follow-Up Questions

  • What causes UnboundLocalError and how does it relate to shadowing? — Python decides at compile time whether a name is local (if any assignment exists in the function). Reading the name before its assignment gives UnboundLocalError, even though a global exists.
  • How do global and nonlocal keywords change shadowing behavior? — global declares the name binds to the global scope. nonlocal declares it binds to the nearest enclosing function scope. Both prevent Python from creating a new local binding.
  • Why is shadowing a built-in like 'list' or 'id' particularly dangerous? — The shadowing affects the entire module scope from that point forward, silently breaking all code that expects the built-in behavior. It's a module-wide bug, not just a function-local one.
  • How does Python's loop variable scoping differ from JavaScript's let? — Python loop variables (for x in ...) leak into the enclosing function scope. JavaScript's let is block-scoped and doesn't leak. Python's var equivalent (no keyword) historically leaked; let was added to fix this.

Related Questions

  • CAP theorem
  • SQL vs NoSQL
  • Big O basics
  • N+1 problem
  • AI tools in workflow

Can You Explain This Cold?

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
GrindQuestionsAITechnical interview assessment
TermsPrivacyAbout