When an inner declaration reuses an enclosing name, which binding does the scope chain give the inner body?
answer
- nearest declaration answers first
- hidden for a region of text
- the outer binding still exists
- declaration, not assignment
- decided by nesting, not by running
basics
~10 sThe innermost declaration wins inside its own region: the outward search stops at the first one it meets. The enclosing binding is hidden there, not destroyed, and code outside that region still sees it.
solid answer
~40 sResolution walks outward from the reference and stops at the **first** region that declares the name, so an inner declaration **shadows** an enclosing one for the whole region it governs. Two things follow. First, the inner body cannot reach the outer binding by that name at all - it is hidden, and reaching it requires a different name or an explicit qualification the language provides. Second, the outer binding is otherwise untouched: it keeps its value, and any code outside the inner region, including code that runs after the inner region has finished, still resolves the name to it. Shadowing is a property of the text, which is why a reader can spot it without running anything, and why it is the one name-resolution hazard a review can reliably catch.
code
pseudocode · 10 lineslet width = 80
function inner()
let width = 40 // shadows the enclosing width, inside this body only
return width // 40
function outer()
return width // 80 - nothing nearer declares it
// after both calls, the enclosing width is still 80go deeper
Remember the rule as nearest-declaration-wins. When two declarations share a name, the one closer to the reference in the text is the one the body sees.
Explain that the outer binding is hidden rather than replaced, that the inner one lives only as long as its region, and that writing a declaration where an assignment was meant silently discards the intended update.
Catch it in review: for every new inner declaration, ask whether the text around it already declares that name, and treat a declaration-instead-of-assignment as a real bug rather than a style note.
Decide the team's line on deliberate shadowing and on whether the warning is an error in the build. The value at stake is that a reader can determine which binding a body means without running anything.
## First match wins, and the search goes outward Name resolution under lexical scoping is a walk: start at the reference, ask the innermost region containing it whether it declares the name, and if not move to the next region out. The walk stops at the **first** hit. Shadowing is just what that rule looks like when two regions on the same path both declare the name - the inner one is met first, so the outer one is never reached from inside. The regions that can shadow include: - A parameter shadowing a name declared in the enclosing text. - A local in a function body shadowing a name from the enclosing module or file. - A local in an inner block shadowing a local of the function that contains the block. - A name declared in a nested function shadowing one in the function that lexically contains it. ## Hidden, not destroyed The outer binding survives shadowing completely. It keeps its value, other regions still resolve the name to it, and once control leaves the shadowing region the name means what it always meant. Nothing was overwritten; a second, separate binding simply occupied the name for one stretch of text. This matters because the two mistakes people make about shadowing are symmetric: | Mistaken belief | What is actually true | |---|---| | The inner declaration overwrote the outer value | Two distinct bindings exist; the outer one is unchanged | | The outer name is gone for the rest of the program | It is unreachable only inside the shadowing region | | Assigning to the inner name updates the outer one | The assignment targets the inner binding alone | | Shadowing is decided when the code runs | It is decided by the nesting of the text | ## Why it is a hazard worth naming Shadowing is legal and often deliberate - a short inner name for a narrowed version of an outer value is perfectly readable. It becomes a defect in two situations: 1. **Unintentional reuse.** Someone adds a local whose name collides with an enclosing one they did not know about. The body now silently reads a different binding, and if both hold the same kind of value the code still compiles and often still runs. 2. **Intended update.** Someone writes a declaration where they meant an assignment. They believe they are updating the enclosing value; they are creating a fresh binding that dies with the region. The outer value never changes, and the bug shows up wherever that outer value is read later. Because the rule is textual, both are visible on the page. Many languages will warn about a shadowing declaration, and where they do not, a reviewer can still catch it by asking one question of every inner declaration: *is there a name like this in the text around it?* ## The connection to captured names For a function that refers to names from its surroundings, shadowing decides **which** of two same-named declarations it refers to - and that decision is made where the function is written, not where it is called. A helper written inside a region that shadows an outer name refers to the inner one forever, wherever it is later invoked and however long after. That is a direct consequence of the outward-from-the-definition walk: the chain a helper's names are resolved against is the chain at its definition site. So the review habit for a helper that refers to surrounding names is: - Identify the free names in the body. - Walk outward through the text from the definition, not from any caller. - At each level, check whether a nearer declaration of that name appears before the one you expected. - If the nearer one is not what you meant, rename one of the two; qualification tricks are a weaker fix because the next reader must repeat your analysis. ## A note on where the rules genuinely differ Languages differ in the details around shadowing - some warn on it, some forbid it in particular region kinds, some let an inner region refer to the shadowed outer binding through an explicit qualification, and some treat a whole body as one region so that a declaration anywhere in it governs the entire body. Those specifics belong to each language's own rules. What is common to all of them is the shape described here: resolution walks outward from the reference, stops at the first declaration it finds, and the declaration it skipped past is hidden rather than replaced.
- Does assigning to the shadowing name change the enclosing binding?No. The assignment targets the inner binding, which is a separate one that lives only as long as its region. The enclosing binding keeps its value, and code outside the region reads the same value it would have read had the inner declaration never existed.
- How does shadowing at the definition site affect a function written there?It fixes which of the two same-named declarations that function's free names refer to. Because resolution walks outward from the definition, the choice is settled where the function is written and does not change wherever or whenever it is later called.
- Is shadowing always a defect worth removing?No. A short inner name for a narrowed version of an outer value reads well. It is a defect when the reuse was unintentional, or when a declaration was written where an assignment to the outer binding was meant - the second case silently discards the update.
saying these in an interview costs you the question
- Says the inner declaration overwrites the outer value.
- Believes the outer name is unusable for the rest of the program.
- Thinks assigning to the inner name updates the enclosing binding.
- Says shadowing is resolved by the caller at run time.
- Assumes only a function body, never a block, can shadow.