skip to content

When you set the scoping rules for a new automation scripting language, should an inner block be allowed to shadow an outer name?

level: principalimportance: should knowfreq 32%

answer

  1. three policies, none obviously right
  2. freedom to write versus confidence to read
  3. who owns the fragility, inner or outer
  4. carve out cross-kind and parameter cases
  5. decide alongside declaration ordering

basics

~20 s

There are three defensible policies — permit silently, permit with a warning, or reject — and the choice trades authoring freedom against the reader's ability to predict what a name means. A common landing point is permit with a warning, and reject only where the collision is across kinds.

solid answer

~50 s

Permitting shadowing silently maximises local freedom: an author writing a nested block never has to know what names exist above them, and adding a name to an outer scope can never break an inner block. Rejecting it maximises the reader's confidence: within a file, one name means one thing. Both have a real cost. Silent shadowing hides the case where an author *meant* the outer variable and accidentally declared a new one, which type checks rarely catch because the two usually have the same type. Rejection makes outer scopes fragile in the opposite direction: introducing a name at the top of a widely included script can invalidate blocks written long before. Permitting with a warning keeps the freedom and surfaces the accident, at the cost of a diagnostic that teams must actually act on. Whatever you pick, apply the same rule to every scope kind — an inconsistent rule is worse than either extreme.

go deeper

for a junior

Recall that a name declared inside a block can hide one of the same name outside it, and that whether a tool complains about that is a choice the language made.

for a middle

Explain how the choice sits on top of the mechanism — per-scope tables and an outward walk make shadowing well defined, so permitting, warning or rejecting is policy rather than capability.

for a senior

Show what each policy does to a real codebase: where the fragility moves, which accidents stay silent, and which narrow cases are worth rejecting even under a permissive rule.

for a principal

Own the trade explicitly: freedom for the inner author versus confidence for the later reader, decided together with declaration ordering, stated once and applied uniformly to every scope kind.

## Why this is a decision and not a fact The mechanism is not in question: a per-scope symbol table plus an outward first-hit walk makes shadowing well defined, and there is no technical difficulty in permitting it. What is in question is whether a language *should*, and that is a judgment about who bears which risk — the author of the inner block, the author of the outer scope, or the person reading the script six months later. ## The three policies and what each optimises | Policy | What it optimises | The cost it accepts | |---|---|---| | Permit silently | Local authoring freedom; an outer scope can gain names without breaking anything below | An author who meant the outer variable and accidentally declared a new one gets no signal | | Permit with a warning | The same freedom, with the accidental case surfaced | Only works if the warning is visible in the workflow and not routinely ignored | | Reject | The reader's certainty: one name, one meaning within a file | Outer scopes become fragile — adding a name at the top can invalidate blocks written long before | The asymmetry worth noticing is that the first and third policies move the fragility to opposite ends. Silent shadowing makes the *inner* block risky to write; rejection makes the *outer* scope risky to extend. For an automation language where scripts routinely include shared preamble definitions that many teams edit, that second fragility is the larger operational problem: a one-line addition to a shared preamble breaking unrelated scripts is a much worse Monday than a warning nobody reads. ## The cases that are not really shadowing A policy stated as one rule usually needs three carve-outs, and naming them is what separates a considered answer from a slogan: 1. **Same-scope collision is never shadowing.** Two declarations of one name in one table have no outward walk to disambiguate them, so this is an error under every policy. Do not let a permissive shadowing rule leak into this case. 2. **Shadowing across kinds deserves harsher treatment.** A local variable hiding a step name, or a loop variable hiding a parameter, produces errors that read as nonsense — a name that used to be callable suddenly is not. Rejecting cross-kind shadowing while permitting same-kind shadowing is a defensible refinement. 3. **A parameter shadowed by a local in the immediately enclosing body is almost always a mistake.** It is the one shape where the author demonstrably had the outer name in hand and still redeclared it. Many designs reject exactly this narrow case even when they permit shadowing generally. ## The adjacent decision you cannot avoid Shadowing policy interacts with whether a scope's declarations are order-independent. If a block's declarations are all collected before its references are resolved, then a reference *above* an inner declaration still resolves to that inner declaration — so a statement can silently change meaning as soon as someone appends a declaration later in the same block. If the block instead requires declaration before use, the same reference keeps resolving outward, and the inner declaration only affects statements below it. These two choices produce genuinely different user experiences from the same shadowing rule, so decide them together rather than one at a time. ## How to decide, and how to defend it A reasonable line of argument for an automation language whose scripts are edited by many teams: - Permit shadowing between nested scopes, because forbidding it makes shared outer scopes unsafe to extend, and extension is continuous. - Warn on it by default, and make the warning cite both declaration positions, so the accidental case is visible at the moment it is written rather than during an incident. - Reject the narrow cases where the author plainly had the outer name in view: a parameter shadowed inside its own body, and any collision within one scope. - Require declare-before-use in block scopes, so appending a declaration to a block cannot retroactively change what the statements above it mean. - Apply the rule uniformly across every scope kind. An inconsistent rule is worse than either extreme, because the reader can no longer answer *which declaration does this name mean* by reading the code — which was the entire purpose of resolving names at all. The part to say out loud in an interview is the framing rather than the verdict: this is a trade between the freedom of whoever writes the inner block and the confidence of whoever reads it later, and there is no answer that is correct independent of who edits the scripts and how often shared outer scopes change.

  • Why is rejecting shadowing outright risky for a language with shared preamble scripts?
    Because it makes outer scopes unsafe to extend. Adding one name to a widely included preamble can invalidate nested blocks written long before by people who never saw it, turning a routine one-line addition into a breaking change for unrelated scripts.
  • Why does a type check rarely catch accidental shadowing?
    Because the author who meant the outer variable almost always declares the new one with the same type — that is why they reached for the same name. The program is well typed and does the wrong thing, which is exactly the class a warning exists for.
  • How does declaration ordering change the effect of a shadowing rule?
    If a block's declarations are collected before its references are resolved, appending an inner declaration retroactively changes what statements above it mean. Requiring declaration before use confines the effect to statements below, so the two decisions have to be made together.

saying these in an interview costs you the question

  • treats shadowing as objectively a bug in all languages
  • says the rule has no effect on anyone but the inner block's author
  • argues rejection is free because authors can rename
  • ignores the same-scope collision case entirely
  • claims type checking already catches accidental shadowing