Short Variable Declarations

Understand the := short variable declaration operator in Go, its syntax, type inference, function-body restriction, redeclaration rules, and common pitfalls.

The := operator is Go’s compact way to declare and initialize a variable at the same time. It is called a short variable declaration. Under the hood it is equivalent to a var declaration with implicit type, but it saves you from writing var and the type name—the compiler figures out the type from the value on the right.

count := 10
message := "hello"

Both variables are fully declared and ready to use. The code above behaves exactly the same as:

var count = 10
var message = "hello"

The compiler infers int for count and string for message. You cannot add an explicit type to the left side of :=; the syntax does not allow it.

Syntax and Basic Usage

A short variable declaration follows the pattern:

identifierList := expressionList

Every identifier on the left must be paired with a value on the right. The right‑hand side can be a single expression, a comma‑separated list of expressions, or a function call that returns multiple values.

name, age := "Alice", 30
x, y := 10.5, 20.3
file, err := os.Open("data.txt")

In the last line, os.Open returns two values: a *File and an error. The := declares file as a *os.File and err as an error—both types are inferred from the function’s return signature. This is one of the most common uses of short declarations in Go, because many standard library functions return a result and an error together.

Initialization is mandatory:

Unlike var, the short declaration must provide an initial value for every variable. A line like x := is a syntax error. This is by design: := is meant for moments when you know the value right away.

Where Short Declarations Can Be Used

Short variable declarations work only inside functions. At the package level, every statement must begin with a keyword such as var, func, const, or import. The := operator is not a keyword, so the parser rejects it outside a function body.

package main
var x = 10   // ok: keyword var at package level
y := 20      // compile error: non-declaration statement outside function body
func main() {
    z := 30  // ok: inside a function
}

This restriction shapes how Go programmers structure code. Package‑level state requires explicit var declarations, while function‑local variables benefit from the brevity of :=. It also means you cannot accidentally create a package‑level variable with a typo or shadowing through :=—the compiler will stop you.

Compile error outside functions:

Attempting to use := at package level produces:
non-declaration statement outside function body.
Only var (or const) can create package‑level variables.

Short declarations can also appear in the initializer of if, for, and switch statements. This is a convenient way to create variables that are scoped tightly to the statement.

if x := computeValue(); x > 100 {
    fmt.Println("large value:", x)
}
// x is not visible here

The variable x exists only inside the if block and its else branches—a pattern you will see frequently when checking errors or processing results.

Redeclaration with := in the Same Scope

A unique rule makes := more flexible than a simple shorthand for var. In a multi‑variable short declaration, one or more of the variables on the left may already be declared in the same block, as long as at least one of the non‑blank variables is new. When this happens, := acts as an assignment for the existing variables and a declaration for the new one. The existing variables must keep the same type.

package main
import "fmt"
func main() {
    a, b := 10, 20      // both a and b are new
    fmt.Println(a, b)   // 10 20
    b, c := 30, 40      // b already exists, c is new → ok
    fmt.Println(b, c)   // 30 40
    b, c = 50, 60       // both already declared → use =, not :=
    fmt.Println(b, c)   // 50 60
}

In the second line, b is redeclared—really reassigned—while c is introduced. The compiler checks that b was originally int and stays int. This rule is invaluable when dealing with error values: you often want to reuse an err variable while declaring a new result variable.

f, err := os.Open("first.txt")
// ...
g, err := os.Open("second.txt") // err reused, g is new

Without this rule, every call that returns an error would need a differently named error variable or an explicit var err error before the call, cluttering the code.

All variables must be of the same type as before:

If you try to redeclare a variable with a different type, the compiler will report a type mismatch. The existing variable’s type is fixed at its first declaration. For example, changing err from error to int via := is never allowed.

The redeclaration only works inside the same block. If you move to an inner block (created by if, for, etc.), := will create a new variable that shadows the outer one—this is normal variable shadowing, not the redeclaration rule covered here. The “same block” requirement means that consecutive lines at the same indentation level inside a function can take advantage of this behaviour.

Redeclaration works seamlessly with multi‑return functions:

If you see code like:
data, err := fetchData(); followed later by moreData, err := fetchMoreData();
and the compiler is happy, you are looking at the redeclaration rule in action. The programmer is reusing err correctly while introducing a new variable.

Comparing := and var Declarations

Although := and var both create variables, they differ in several important ways.

Aspectvar declaration:= short declaration
Where allowedPackage level and function levelFunction level only (including if, for, switch initializers)
Explicit typeOptional; can be specifiedNot allowed; type always inferred
InitializationOptional; missing value yields zero valueMandatory; every variable must have a value
Syntaxvar name type = valuename := value
Package‑level variablesYesNo

Choose var when:

  • You are at package level.
  • You need to declare a variable with a zero value and assign later.
  • You want to make the type explicit for readability or documentation.
  • You are grouping multiple declarations for a block.

Choose := when:

  • You are inside a function and know the value immediately.
  • You want shorter, more readable code.
  • You are capturing multiple return values, especially the classic value, err := ... pattern.

The two forms can coexist freely. A typical function might use var for a variable that accumulates results across a loop, and := for intermediate values.

func process(items []int) int {
    var total int                  // zero value, accumulate later
    for _, item := range items {
        doubled := item * 2        // short declaration inside loop
        total += doubled
    }
    return total
}

Common Mistakes and Misunderstandings

Even experienced Go developers can trip over these edge cases.

Trying to use := when no new variable exists. The compiler error no new variables on left side of := means every identifier on the left is already declared in the current block. You must use = instead.

x := 10
x := 20   // error: no new variables on left side of :=
x = 20    // correct

Believing := always creates a new variable. Because of the redeclaration rule, an existing name may be reassigned, not created. This is intentional and safe as long as the type remains the same.

Forgetting that := does not work at package level. This is especially confusing when moving code from a function to global scope during refactoring. The fix is to switch to var.

Shadowing with := in inner blocks. A short declaration inside an if or for block creates a brand‑new variable, even if an outer variable has the same name. The outer variable becomes invisible inside that block.

x := 1
if true {
    x := 2       // new x, shadows outer x
    fmt.Println(x) // 2
}
fmt.Println(x)   // 1 — outer x unchanged

This is a common source of bugs. The redeclaration rule (same block) does not apply across block boundaries.

Using := when you meant to assign to the outer variable:

If you mean to update the outer variable, use = without :=, and declare the inner variable (if any) separately.
Example: var err error followed by assignment is clearer when you are not sure about re‑declaration.

Using := with a single underscore. The blank identifier _ can appear on the left side of :=, but it does not count as a “new variable” for the purpose of the redeclaration rule. If all non‑blank names are already declared, you will still get the “no new variables” error.

_, err := someFunc() // ok: err is new
_, err := otherFunc() // error: no new variables on left side of :=

To reuse err with a discarded first return, you need to ensure at least one other non‑blank identifier is new.

Summary

The short variable declaration is more than a syntactic sugar; the ability to redeclare existing variables in the same block, combined with the strict function‑body limitation, shapes how Go code is written. It nudges you toward small, self‑contained functions where local variables are declared and initialized in one motion, and where error values flow naturally without a parade of differently named err1, err2 variables.

The most important thing to carry forward: := is for inside functions, with values you have right now. When you need package‑level variables, zero‑value initialization, or explicit types, reach for var.