skip to content

An operation introduces a placeholder named the same as its enclosing type's - which one does that operation's body see?

level: middleimportance: nice to knowfreq 26%

answer

  1. inner scope wins
  2. a second name, not a reuse
  3. two types, one spelling
  4. buffer rejects the argument
  5. rename rather than convert

basics

~20 s

The operation's own placeholder wins inside that operation. The inner name hides the enclosing type's for the whole body, so the two are unrelated types that happen to share a spelling, and stored state typed by the outer one will not accept the inner one.

solid answer

~40 s

Inner scope wins, exactly as it does for ordinary variables. Re-declaring the name on the operation introduces a **new** placeholder chosen per call, and inside that body the old spelling now refers to the new name; the enclosing type's placeholder becomes unreachable there. The practical symptom is a confusing error rather than a wrong result: the body tries to append its argument to a buffer typed by the outer name and is rejected, because the two are different types with the same label. The fix is a rename, not a cast - either the operation genuinely needs its own name, in which case call it something else, or it meant the enclosing one, in which case it should not re-declare it at all.

code

pseudocode · 8 lines
pseudocode
type Connection<M>:
    outbox: list of M

    function send(message: M):
        outbox.add(message)          // M here is the connection's

    function relay<M>(message: M):   // a NEW M hides the outer one
        outbox.add(message)          // rejected: unrelated types

go deeper

for a junior

Know the direction: the name declared closest to the code wins, so an operation's own placeholder hides the enclosing type's for that body.

for a middle

Explain that the re-declaration creates a second, unrelated name with per-call scope, and predict the symptom - stored state typed by the outer name refusing the inner one.

for a senior

Recognise the confusing error on sight, trace it to the scope chain, and insist on a rename rather than a conversion that would hide a genuine naming mistake.

for a principal

Treat it as a conventions question: distinct, descriptive placeholder names across a scope chain remove a whole class of unreadable errors before review ever sees them.

## Shadowing is ordinary scoping, applied to types Every language with nested scopes has a rule for what happens when an inner scope re-uses an outer name: the inner declaration **hides** the outer one for the extent of the inner scope. Type parameters are names in a scope like any other, so the same rule applies. An operation that introduces a placeholder spelled the same as its enclosing declaration's has not *reused* that placeholder - it has declared a **second, unrelated** one and made the first unreachable inside that body. The direction matters and is easy to state backwards. It is **not** that the declaration-level name takes precedence because it is on the outer, more important declaration. The inner name wins, because scoping resolves from the innermost enclosing declaration outward. ## What the confusion looks like Take the chat connection, parameterized by its message type, with a relay operation that re-declares the same name: - Outside the operation, the name means *whatever this connection was built for* - fixed once, shared by the buffer and the other members. - Inside the operation, the same spelling means *whatever this call picked* - fresh each call, related to nothing else. - So appending the operation's argument to the connection's buffer is rejected: the buffer's element type and the argument's type are different types that print identically in the error message. That last detail is why shadowing is worth recognising on sight. The failure is not subtle in behaviour - it is a compile error - but the message can be baffling, because it appears to say that a type is not compatible with itself. | Referred to inside the operation | Referred to elsewhere in the type | |---|---| | The operation's own name, chosen per call | The declaration's name, chosen per instance | | Unrelated to the buffer's element type | The buffer's element type | | Out of scope once the call ends | In scope for the instance's life | ## How it happens Three routine paths lead here: 1. **Copying a member between declarations.** A helper is moved from a stand-alone position into a parameterized type and keeps the placeholder it was written with. 2. **Convention collision.** Single-letter placeholder names are conventional, so an operation written in isolation reaches for the same letter the enclosing declaration used. 3. **Intent drift.** The operation really did need its own per-call name at some point, and nobody noticed the collision with the declaration's. ## What to do about it - **If the operation needs its own unknown**, keep it and give it a different, descriptive name. Two unrelated placeholders should never share a spelling in the same scope chain, however conventional the letter is. - **If the operation meant the enclosing type's**, delete the re-declaration. The name is already in scope; introducing it again is what broke the link. - **Do not reach for a conversion** to make the error go away. A cast or a widening here silences a signal that the two names were never meant to be different, and reintroduces exactly the unchecked step parameterization removed. Languages differ in how loudly they respond: some accept the shadowing silently, some emit a warning about hiding, and some style rules ban re-using an enclosing placeholder's name outright. None of them merge the two names, so the mechanism is the same everywhere - and relying on a particular toolchain to warn you is not a substitute for reading the scope. ## Why an interviewer asks it This is a small, checkable probe of whether the declaration site is understood as **scope** rather than as decoration. Somebody who thinks of a type parameter as "the type this class is about" has no way to explain how a member could possibly see a different one; somebody who thinks of it as a name introduced at a site, with a scope and a lifetime, answers immediately and predicts the error message. It is a differentiator rather than a screening question - it shows up when someone has actually debugged one.

  • Can the body of the shadowing operation still reach the enclosing declaration's placeholder?
    Not by that name - the spelling is taken for the whole body. It can still work with values whose types were fixed by the outer name, such as the contents of a field, but it cannot name that type, which is why the fix is a rename rather than a way of qualifying the outer one.
  • Is shadowing a type parameter ever deliberate and reasonable?
    Rarely, and usually only where an operation was written to stand alone and its name is meaningful on its own terms. Even then the collision costs more in reading time than the name saves, so the standard advice is to keep placeholder names distinct across a scope chain.

saying these in an interview costs you the question

  • Says the enclosing type's placeholder wins inside the operation.
  • Believes two placeholders with the same name are automatically the same type.
  • Fixes the resulting error with a conversion instead of a rename.
  • Thinks re-declaring the name simply restates the enclosing one.
  • Assumes every toolchain rejects or warns about the collision.