In Go, what does `:=` inside an if block do when the enclosing function already declares that name?
answer
- two variables, one name
- declares, does not assign
- the inner one dies at the brace
- reuse only within the same block
- the outer value is unchanged afterwards
basics
~20 sIt declares a new variable that lives only until that block's closing brace. The outer variable of the same name is hidden, not assigned, so whatever the inner block writes is lost when the block ends.
solid answer
~50 sA short variable declaration always declares in the innermost enclosing block, so `:=` inside an if body creates a fresh variable that shadows the outer one and disappears at the closing brace. The outer variable is never touched: it still holds whatever it held before, which is why a function can return a nil error after an inner block clearly assigned to `err`. The redeclaration rule that lets `b, err := f()` reuse an existing `err` only applies when that `err` was declared earlier in the *same* block and has the same type; crossing a brace makes it a new variable instead. This compiles cleanly — Go rejects unused variables, but the inner one is used inside its block, so nothing complains. The fix is to use `=` (plain assignment) when you mean the outer variable, or to give the inner value a different name.
code
go · 9 linesfunc shadow() {
n := 1
{
n := 2 // a new variable; the outer n is hidden here
n++
_ = n
}
fmt.Println(n) // prints: 1
}go deeper
Be ready to say plainly that := declares rather than assigns, and that a declaration inside braces makes a second variable that vanishes at the closing brace. Being able to predict the printed value of a two-line example is the whole bar here.
Explain the mechanics: scope begins at the end of the declaration and ends at the innermost block, and the reuse rule for := requires the same block, the same type, and one new name. Say why the compiler is silent.
Show that you know which real bugs this produces and how you keep them out of code you own: narrow declarations, distinct names for distinct values, and tests that exercise the failure branch rather than only the happy path.
Own the position that shadowing is a legal feature, not a defect to ban outright. Be able to argue where a team should spend enforcement effort and where a blanket rule would just add noise to reviews.
## What `:=` actually does A short variable declaration (`x := expr`) is a *declaration*, not an assignment. Go's declaration rules say that an identifier declared inside a function is scoped to the innermost block that contains the declaration — a block being anything between `{` and `}`: a function body, an if body, an else branch, a for body, a switch case, or a bare `{ … }`. The scope starts at the end of the declaration itself and ends at that block's closing brace. So when a name already exists further out and you write `:=` inside a nested block, you get two distinct variables with the same name. The inner one *shadows* the outer one: for the rest of that block, the name refers to the new variable, and when the block ends, the new variable is gone and the name refers to the outer variable again — unchanged. ```go n := 1 { n := 2 // a second variable, also called n n++ // touches the inner one only _ = n } // n is still 1 here ``` ## Why the compiler stays silent Two Go rules make this easy to write by accident and hard to notice: 1. **Shadowing is legal.** It is not a warning, not a vet check in the default set, and not a style violation the compiler knows about. Inner scopes hiding outer names is a normal, useful feature — it is how a small helper variable in a loop body avoids colliding with anything else. 2. **The unused-variable error does not fire.** Go refuses to compile a declared-but-unused local, which catches many typos. But a shadowed variable is usually *used* inside its block (checked, logged, returned from the block), so the rule is satisfied and the code builds. ## The redeclaration rule people misremember Go allows `:=` to appear with a name that already exists, on one condition: at least one non-blank name on the left must be new, and the reused names must have been declared *earlier in the same block* with the same type. When that holds, the reused name is simply assigned — no second variable appears. ```go a, err := strconv.Atoi("1") // a and err are both new b, err := strconv.Atoi("2") // b is new; err is assigned ``` That behaviour is what makes the shadowing case surprising. Developers internalise "`:=` reuses `err`" from same-block code, then carry the intuition across a brace, where it stops being true: inside a nested block, *every* name on the left is new, because the reuse rule requires the same block. ## Where it bites The classic shape is a value that must survive the block — most often an error, sometimes a result or a counter: ```go var err error if needsPatch { err := writeHunk(h) // new err, dies at the brace if err != nil { return nil // or: log and carry on } } return err // the outer err, still nil ``` The program is well-typed, the tests over the happy path pass, and the failure is reported as success. ## How to write it so the question does not arise - Use `=` when you mean the variable that already exists. If no such variable is in scope, the compiler tells you immediately — a useful, cheap check. - Give inner values their own names (`writeErr`, `parseErr`) when they genuinely are a different thing. Two variables with the same name in one function is a readability cost even when it is correct. - Declare a variable in the narrowest scope that needs it. Most accidental shadows come from an outer variable that was hoisted further out than necessary. - Keep functions short enough that both declarations fit on one screen. Shadowing is nearly invisible when the two declarations are eighty lines apart. ## One detail worth knowing Because the new variable's scope begins *at the end* of the declaration, the right-hand side still sees the outer variable. `x := x` inside a nested block is legal and idiomatic: it makes a local copy of the outer `x`. That is the same rule read from the other direction — the inner name does not exist yet while its own initialiser is being evaluated.
- In `b, err := f()` where `err` already exists in the same block, is a second `err` created?No. A short variable declaration may reuse names that were declared earlier in the same block with the same type, as long as at least one non-blank name on the left is new. The reused name is assigned, not redeclared, so there is still exactly one `err`. Cross a brace and the rule no longer applies: inside a nested block every name on the left is new.
- Why does `x := x` compile inside a nested block?The scope of a variable declared inside a function begins at the *end* of its declaration. While the initialiser on the right is being evaluated, the new `x` does not exist yet, so the right-hand `x` resolves to the outer one. The statement makes a local copy of the outer value, which is a deliberate and common idiom.
- Does the Go compiler or `go vet` warn about a shadowed variable?Neither, by default. Shadowing is legal Go, and the unused-variable error does not fire because the inner variable is normally used inside its own block. Detecting it takes the separate `shadow` analyzer from `golang.org/x/tools`, or a reviewer noticing the brace.
saying these in an interview costs you the question
- Says := always assigns to an existing variable of that name
- Claims the compiler rejects or warns about shadowing
- Thinks the value written inside the block survives it
- Believes only package-level names can be shadowed
- States that Go's unused-variable error catches shadowed variables