skip to content

Scope and Block Structure

Which declaration a name resolves to, and how long the value behind it lives — lexical versus dynamic scope, shadowing, and block-bound release. Interviewers separate scope from lifetime.

on this pageshow

questions

5

When a nested block declares a name that an enclosing block already declares, what happens to references to that name inside and after that block?

level: juniorimportance: must knowfreq 62%

answer

  1. two bindings, not one
  2. innermost declaration wins
  3. hidden, not overwritten
  4. effect confined to the inner block
  5. declaration shadows, assignment writes through

basics

~10 s

The inner declaration shadows the outer one: inside that block the name resolves to the new declaration, while the outer binding keeps its own value, untouched, and becomes visible again once the block ends.

solid answer

~40 s

Two separate bindings now exist. Name resolution starts in the innermost block and walks outward, so from the inner declaration to the end of that block every mention of `width` finds the inner binding; the outer `width` is hidden, not modified. Once the block ends, resolution reaches the outer binding again with whatever value it had all along. That is exactly what separates shadowing from assignment: a declaration creates a second binding whose effect is confined to the block, while an assignment writes through to the one binding the name already had and is visible afterwards. Shadowing is legal in most block-structured designs but hostile to readers, because the meaning of a mention now depends on which side of a block boundary the line sits.

code

pseudocode · 8 lines
pseudocode
declare width = 80
render_header(width)              // 80

for each section in sections
    declare width = section.width // a second binding, this block only
    render_body(width)            // the section width

render_footer(width)              // 80 again - outer binding never written

go deeper

for a junior

Be able to say it in one sentence: the inner declaration hides the outer one inside that block and does not change its value. Naming the two bindings out loud is most of the answer.

for a middle

Explain resolution as a search outward from the innermost block, and contrast a declaration with an assignment by saying what each one leaves behind after the block ends.

for a senior

Show how a shadowed name becomes a defect during ordinary maintenance - a moved line, a deleted declaration, a merge - and say how you would catch it in review or with a build setting.

for a principal

Decide whether the codebase forbids shadowing outright or permits it in narrow cases, and weigh the churn of a rename sweep against the class of quiet defects it removes.

## Two declarations, one name A **block** is a region of program text that can hold declarations of its own: a routine body, a loop body, a conditional arm, or a bare nested block used only to group statements. A **declaration** introduces a *binding* - a pairing of a name with a storage slot or a value. A **mention** of a name in the program text resolves to exactly one binding, and the rule that picks it is the whole subject here. **Shadowing** happens when a block declares a name that some enclosing block already declares. Nothing is overwritten and nothing is destroyed. There are now two bindings that happen to share a spelling, and the inner one is simply *nearer* to the mentions inside that block. ## How resolution picks the winner Resolution in a block-structured program is a search **outward**, never inward: 1. Start in the innermost block that contains the mention. 2. If that block declares the name, bind the mention to that declaration and stop. 3. Otherwise repeat the step in the block that encloses it. 4. If no enclosing block declares the name, the mention is unresolved and the program is ill-formed. Because the search stops at the **first** match, an inner declaration hides an outer one for every mention in its region. The instant the inner block ends, the search performed for later mentions reaches the outer declaration again. Two consequences follow at once: - The hidden binding **keeps its value**. Shadowing is a resolution effect, not a write; no step of the program touches the outer storage. - The hiding is **positional**, not temporal. It covers the text of the inner block, regardless of how often that block runs or whether it runs at all. ## Shadowing is not assignment The two edits look almost identical on the page - one line mentioning a name - and they differ in everything that matters. | | inner declaration (shadowing) | assignment to the outer name | |---|---|---| | bindings afterwards | two | one | | what the inner block writes | the new binding | the existing binding | | value seen after the block ends | unchanged | the written value | | effect visible to the enclosing block | none | yes | | deleting the inner line later | changes which binding is hit | no change in meaning | This table is the answer to most follow-ups on this subject. An interviewer asking about shadowing is usually checking whether a candidate confuses *declaring* with *assigning*, because that confusion produces defects that compile cleanly. ## Why reviewers treat a shadowed name as a hazard Shadowing is legal, and sometimes deliberate - a short inner name for a narrowed value can read well. It is still the kind of construct that turns ordinary maintenance into a defect: - **Moving a line across the boundary** changes which declaration it resolves to, silently and legally. - **Deleting the inner declaration** during a cleanup leaves the mentions in place; they now read the enclosing binding and the program still builds. - **Merging two edits** can introduce a shadow neither author wrote, because neither side saw the other's declaration. - **Reading the code** costs more: a reviewer must track the block nesting to know which of two same-spelled names a line means. - **Renaming tools and diffs** stop being trustworthy signals, because the same spelling now denotes two things. The usual remedy is not cleverness but distinct names, plus a build setting that reports a shadowing declaration where the codebase has decided against it. Neither costs anything at run time. ## What an interviewer is listening for The strong answer is short and contains three claims, in this order: - there are now **two bindings**, not one; - resolution takes the **nearest enclosing declaration**, so the inner one wins inside its block; - the outer value is **unchanged** and reachable again after the block ends. A weak answer says the inner declaration "overwrites" or "replaces" the outer variable. That single word is the defect, because it predicts the wrong value after the block. If you are asked to demonstrate rather than describe, write four lines: a declaration, a nested block that redeclares and writes, and a read after the block that still shows the original value. One boundary is worth stating out loud, because it earns credit: shadowing is about *which declaration a mention resolves to*. How long the value behind either binding survives is a separate question, and the two can differ.

  • How is shadowing different from assigning to the enclosing block's variable?
    An assignment writes through to the one binding the enclosing block already made, so the new value is still visible after the block ends. A declaration makes a second binding that stops mattering when the block ends, leaving the outer value exactly as it was. The two edits look alike on the page and have opposite effects on the surrounding code.
  • If shadowing is legal, why do reviewers still treat it as a hazard?
    Because the meaning of a mention now depends on its position. Moving a line across the block boundary, or deleting the inner declaration during a cleanup, silently changes which binding it hits, and both versions are well-formed. Distinct names remove the ambiguity at no run-time cost.

saying these in an interview costs you the question

  • Says the inner declaration overwrites the outer variable's value
  • Thinks the outer name is lost for the rest of the program
  • Treats a shadowing declaration and an assignment as the same edit
  • Claims the most recently executed declaration wins, wherever it sits
  • Believes both bindings can be read through the same bare name
open as a page

A helper reads a page-width setting it never declares: under lexical scope versus dynamic scope, which declaration wins?

level: middleimportance: must knowfreq 55%

basics

~20 s

Under lexical scope the read binds to the declaration in the surrounding program text, decided before the program runs. Under dynamic scope it binds to the most recent still-active declaration on the chain of calls, so the caller decides.

open as a page

A buffer declared inside a section block is stored in a document that outlives it: what ends when the block exits?

level: juniorimportance: should knowfreq 52%

basics

~20 s

Only the name's scope ends. Scope is the region of program text where a name can be used; lifetime is the stretch of run time during which the value exists, and the document's reference keeps that value alive.

open as a page

A running total prints zero after an edit added a declaration inside the loop block: how do you diagnose which binding is written?

level: middleimportance: should knowfreq 40%

basics

~20 s

Resolve each mention outward to its nearest declaration and compare them. The write inside the loop reached a new per-iteration binding the edit introduced, while the read after the loop still reaches the outer binding, which nothing ever wrote.

open as a page

A section block opens a scratch file and then exits by raising an error: what does block-bound release guarantee about that file?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Nothing, unless the release was bound to the block's exit rather than written as a trailing statement. A release bound to block exit runs on every path out, the raising one included; a trailing call is simply never reached.

open as a page