Last Updated: May 22, 2026
The := operator declares and initializes a variable in one step, inferring the type from the value on the right. It's the most common way to introduce new variables inside functions, and it shows up in nearly every Go program. This lesson covers the exact rules of :=, how it interacts with scope, and the shadowing pitfall that affects both new and experienced engineers.
:= Actually Does:= is shorthand for "declare a new variable, infer its type from the value, and assign that value." It replaces a var declaration followed by an assignment.
These two snippets do exactly the same thing:
The compiler looks at the right-hand side, figures out the type (float64 for 29.99, float64 for 5.00), and declares the variable with that type. You don't write the type yourself, which is why := is also called the short variable declaration in the Go spec.
Three rules apply:
:= only works inside a function body. You cannot use it at package level.:=. If you need a specific type, use var or a conversion.:=:= is a function-body-only construct. The Go specification is explicit about this. Try to use it at the top of a file and the compiler stops you.
What's wrong with this code?
:= is not allowed outside a function. The compiler responds with:
Fix: Use var at the package level.
This isn't an arbitrary rule. Package-level declarations need to be unambiguous to the compiler when it builds the dependency graph across files. var name = value and var name type both start with the keyword var, which makes parsing simple. Mixing := into the file scope would complicate things, so Go disallows it.
:= accepts multiple variables on the left and multiple values on the right. The counts must match.
Each variable gets the type of its corresponding value. productName becomes a string, price becomes a float64, and inStock becomes a bool. The compiler infers them independently.
This pattern is everywhere in Go because functions often return multiple values, including an error:
strconv.Atoi returns (int, error). The := declares both quantity and err from those two return values. This is the idiomatic way to call any function that returns a value plus an error.
When the left side mixes existing variables and new ones, := re-uses the existing ones and only declares the new ones.
On the second line, customer already exists, so := reassigns it. orderCount is new, so := declares it. As long as one variable on the left is new, the statement is valid.
If every variable on the left already exists in the same scope, the compiler refuses to use :=. You have to use plain = instead.
What's wrong with this code?
Both customer and orderCount already exist in this scope, so there's nothing new to declare. The compiler reports:
Fix: Use = for pure reassignment.
This rule is what makes the error-handling idiom work. In a function that calls several things that can fail, you can keep reusing err while declaring new result variables alongside it:
On the second :=, err already exists but price is new. The rule is satisfied, so := is allowed. This is why you almost never need var err error in real Go code.
:= Interacts with ScopeA variable declared with := lives in the scope where it was declared. That scope is the nearest enclosing block, which is anything between matching curly braces.
This becomes interesting (and dangerous) when := appears inside an if, for, or other nested block. The new variable belongs to that inner block, not the outer one.
discount exists only inside the if block. Trying to print it after the if would fail with undefined: discount.
for loops behave the same way:
The i and p declared by := in the for statement belong to the loop's block. Once the loop ends, they're gone. This is exactly what you want most of the time, but it sets up the most common trap with :=: variable shadowing.
Shadowing happens when := declares a new variable that has the same name as one in an outer scope. The inner variable hides the outer one for the duration of the inner block. The outer variable is untouched, even though it looks like you're updating it.
This is the most common := bug in Go code.
What's wrong with this code?
The author wanted to parse userInput and store the result in the outer quantity and err. But inside the if, they wrote quantity, err := strconv.Atoi(userInput). Because := is used and there's a new scope, both quantity and err are declared as fresh variables inside the `if` block. The outer quantity stays 0, and the outer err stays nil.
The shadowed variables disappeared when the if ended, taking the parsed value with them.
Fix: Declare the inner variable with a different name, or use = to assign to the existing variables.
Switching to = reassigns the outer variables. The parsed quantity now survives outside the if.
Shadowing isn't a performance problem, it's a correctness problem. The compiler accepts shadowed declarations silently. Use go vet -vettool=... with the shadow analyzer, or your editor's linter, to flag these before they ship.
Shadowing can also occur inside for loops, in switch cases, and anywhere else a new block opens. When using := in a nested scope, ask: am I declaring a new variable, or do I mean to update an outer one?
:= Inside if and for Init StatementsGo's if and for statements have an optional short init clause that uses :=. The variable declared there is scoped to the statement, including all branches.
total lives for the entire if/else statement, including the else branch, then disappears. This is a clean way to declare a value you only need for a decision.
The for loop's init clause works the same way:
The i declared in the for init is scoped to the loop. Use this freely. The scoping rule keeps loop counters and temporary values out of the surrounding code.
:= vs varBoth := and var declare variables. They're not interchangeable in every situation, but where they overlap, Go has clear idioms.
| Situation | Use := | Use var |
|---|---|---|
| Inside a function, type is obvious from the value | Yes | Allowed but verbose |
| You want the zero value (no initializer) | Not possible | Yes |
| Package-level declaration | Not allowed | Yes |
| You need to declare a specific type that differs from the literal's default | Not directly | Yes |
| Multiple values from a function return | Yes (idiomatic) | Allowed but verbose |
Declaring an interface variable that starts as nil | Not possible | Yes |
Examples of each:
The general guidance:
:= inside functions when you're initializing with a value. This is the default.var at package scope (since := isn't allowed there).var name type when you want the zero value and no initializer.var name type = value when you need to pin a specific type that differs from the literal's default, like var stock int32 = 250 instead of int.Don't agonize over this. The reader's eye reads := as "introduce a new local thing with this value" and var as "declare a variable, possibly without initializing it yet." Pick the one that communicates intent.
The program below uses := in the situations covered above: multiple assignment from a function, the "at least one new variable" rule for error handling, and scoping inside an if.
Walk through what := is doing in this program:
customer := "Rohit" declares a string.cart := []string{...} declares a slice of strings.totalItems := 0 declares an int.for, qty, err := declares two new variables in the loop's block. They vanish each iteration and get re-declared, which is fine because the loop body opens a fresh block on each pass.if, discount := 0.10 declares a float64 scoped only to the if branch. The else branch can't see it, and neither can the code after the if.That's := in a single short program: declaration, multi-return handling, and block scoping all working together.