What Is the Difference Between var and

= in Go

Use var for explicit types or pre-declaration, and := for concise, type-inferred declarations inside functions.

The variable declaration fork

You write a function in Go, grab a variable with :=, and it works. You move that variable to the top of the file, outside any function, and the compiler rejects the code. You try to reuse := on a variable you already declared, and the compiler rejects that too. Go's variable declaration rules feel stricter than Python or JavaScript, but the restrictions exist to keep code readable and prevent subtle bugs. The language gives you two tools: var for explicit declarations and := for short declarations. Knowing which one to reach for saves you from compiler errors and makes your code idiomatic.

Concept in plain words

var is the full declaration. You use it when you need to specify a type explicitly, declare a variable without an initial value, or declare a variable at the package level. := is the short declaration. It infers the type from the value on the right side and assigns it in one step. You can only use := inside functions. The compiler requires at least one new variable in a := statement; otherwise, it treats the statement as an error.

Think of var as filling out a form where you write the field name, the type of data, and optionally the value. Think of := as handing someone a labeled box. The label and contents come together, and you don't need to describe the box type separately.

Go uses capitalization to control visibility. A variable name starting with a capital letter is exported and visible to other packages. A lowercase name is private to the current package. This applies to both var and :=. The convention is to keep names lowercase unless you need external access. Public names start with a capital letter. Private names start lowercase. There are no keywords like public or private.

gofmt is mandatory. Don't argue about indentation; let the tool decide. Most editors run it on save. The formatter ensures consistent spacing around := and var, so you focus on logic, not style.

Minimal example

Here's the simplest comparison: declare a variable with var, declare one with :=, and see how the compiler treats them.

package main

import "fmt"

// Package-level variables must use var.
// The type is inferred from the string literal.
var greeting = "Hello"

// Explicit type declaration with a zero value.
// count is initialized to 0 automatically.
var count int

func main() {
    // Short declaration infers int from 42.
    // This is only valid inside a function.
    age := 42

    // Reassigning requires the plain assignment operator.
    // Using := here would cause a compile error.
    age = 43

    fmt.Println(greeting, age, count)
}

Walk through what happens

When the compiler processes var greeting = "Hello", it allocates storage for a string at the package level. Since the value is provided, the type is inferred as string. The variable count gets allocated with the explicit type int. Because no value is given, Go initializes it to the zero value for integers, which is 0.

Inside main, the statement age := 42 creates a new variable named age on the stack. The compiler looks at 42, determines it's an untyped integer constant, and assigns the type int to age. The assignment age = 43 updates the existing variable. If you wrote age := 43, the compiler would reject the program with no new variables on left side of :=. The compiler enforces that := introduces at least one new identifier.

Go initializes every variable to its zero value. You don't need to assign null or undefined. An int starts at 0. A bool starts at false. A string starts at "". A pointer starts at nil. A slice starts at nil. This means var count int is equivalent to var count = 0. You can rely on zero values to set up default state. This reduces boilerplate in constructors.

var defines structure. := drives flow. Keep package state explicit and function logic concise.

Realistic example

Real code often involves functions that return multiple values. You use := to capture all return values at once, or var when you need to declare an error variable before a loop and update it repeatedly.

Go's error handling pattern if err != nil is verbose by design. The community accepts the boilerplate because it makes the unhappy path visible. You cannot ignore errors silently. When you use := to capture an error, you must handle it or assign it to _ to discard it intentionally.

Here's a function that reads files and handles errors. The code shows how := captures multiple values and how the blank identifier works.

package main

import (
    "fmt"
    "os"
)

// readFile opens a file and returns the content and an error.
// The return types are []byte and error.
func readFile(path string) ([]byte, error) {
    // Short declaration captures both return values.
    // data is inferred as []byte, err as error.
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }
    return data, nil
}

func main() {
    // Declare err before the loop so it persists across iterations.
    // Using := inside the loop would create a new err each time.
    var err error
    var files = []string{"config.yaml", "secrets.env"}

    for _, file := range files {
        // Reassign err with plain assignment.
        // data is new, so := is valid here.
        // This creates a shadowed err inside the loop block.
        data, err := readFile(file)
        if err != nil {
            fmt.Printf("failed to read %s: %v\n", file, err)
            continue
        }
        fmt.Printf("read %d bytes from %s\n", len(data), file)
    }
}

Sometimes a function returns values you don't need. Go requires you to acknowledge every return value. You use the blank identifier _ to discard a value intentionally. The statement _, err := os.ReadFile(path) says you considered the first return value and chose to drop it. The compiler treats _ as a write-only variable. You can use _ with both var and :=. The convention is to use _ sparingly with errors; discarding an error usually hides a bug. If you must discard a value, document why.

Pitfalls and compiler errors

You cannot use := at the package level. The compiler rejects this with syntax error: unexpected :=. Package-level variables must use var.

You cannot use := to update an existing variable without introducing a new one. The compiler rejects this with no new variables on left side of :=. Use = for updates.

Shadowing variables in loops is a common trap. If you declare err outside a loop and use data, err := ... inside, the := creates a new err that shadows the outer one. The outer err never gets updated. The compiler allows shadowing, so you must check the scope manually. The code in the realistic example demonstrates this shadowing. The var err error outside the loop is unused because the loop creates a new err every iteration.

Variables declared with := are scoped to the block where they appear. If you use := inside an if statement, the variable is only visible inside that block. This is useful for limiting the lifetime of temporary variables. The compiler enforces block scope strictly. A variable declared in an if block cannot be accessed after the block ends.

if value := compute(); value > 0 {
    // value is visible here.
    fmt.Println(value)
}
// value is not visible here.
// The compiler rejects access with: undefined: value

Type inference with := is powerful, but explicit types with var are sometimes necessary. If you assign an untyped constant to a variable, the compiler infers the type. The statement x := 42 makes x an int. The statement var x = 42 also makes x an int. However, if you need a specific type like float64, you must use var. The statement var x float64 = 42 works. The statement x := 42.0 works. The statement x := float64(42) works. If you write x := 42 and later need float64, you must convert explicitly. Go does not perform implicit conversions between numeric types. This prevents accidental precision loss. Use var when the type matters more than the value.

The loop variable capture bug is fixed in Go 1.22. In older versions, the loop variable was shared across iterations. If you launched a goroutine inside a loop, all goroutines captured the same variable. The compiler rejects this with loop variable i captured by func literal. This error forces you to fix the capture. You can fix it by declaring a new variable inside the loop with :=. The statement i := i creates a copy. This pattern is common when launching goroutines in a loop.

Shadowing hides bugs. Check your scopes.

Decision: when to use var vs :=

Use var when you need to declare a variable at the package level. Use var when you want to specify the type explicitly while deferring initialization. Use var when you need to declare a variable without an initial value to get the zero value. Use := when you are inside a function and the type is obvious from the initializer. Use := when you need to declare and assign multiple variables in one line. Use = when you are updating a variable that already exists in the current scope.

var for structure. := for flow. Pick the tool that matches the intent.

Where to go next