Menu

Golang Variables: var, :=, Zero Values and Shadowing

How to declare variables in Go with var and the := short form, what zero values are, multiple assignment, and the shadowing and unused-variable errors that catch new Go programmers.

This page includes runnable editors - edit, run, and see output instantly.

Go has two ways to declare a variable: var, which works anywhere, and :=, a short form that works inside functions and infers the type.

Every variable has a fixed type decided at compile time. When you give a value, Go infers the type from it, so writing the type as well (var city string = "Lisbon") is legal but redundant.

var vs :=

var:=
Wherepackage level and inside functionsinside functions only
Typeoptional; required when there is no valuealways inferred
Initial valueoptional (zero value otherwise)required
Redeclaringneverallowed if at least one variable on the left is new

The idiomatic choice inside a function:

  • x := value when you have a starting value.
  • var x T when you want the zero value, which signals "empty on purpose": var buf bytes.Buffer, var total int.
  • var x T = value when the inferred type is not the one you want, for example var ratio float64 = 1 (plain ratio := 1 would be an int).

:= outside a function is a syntax error:

./main.go:5:1: syntax error: non-declaration statement outside function body

Zero Values

A variable declared without a value is never garbage. It holds its type's zero value:

TypeZero value
integers, floats0
boolfalse
string""
pointers, slices, maps, channels, functions, interfacesnil
arrays, structsevery element or field set to its zero value

Good Go APIs are designed so the zero value is useful: a zero sync.Mutex is unlocked, a zero bytes.Buffer is empty and ready, and len, range and append all work on a nil slice. The one to watch is the map: reading a nil map returns zero values, but writing to one panics. Create maps with make or a literal before storing into them.

Declaring Several Variables

The var ( ... ) block groups related package-level variables. In a multiple assignment like x, y = y, x, every expression on the right is evaluated before anything is assigned, which is why the swap works.

:= with several names redeclares nothing as long as at least one name on the left is new. Existing names in the same scope are simply assigned. This is what lets you reuse err:

f, err := os.Open("a.txt")   // declares f and err
g, err := os.Open("b.txt")   // declares g, assigns to the existing err

If none of the names are new, it is an error:

./main.go:7:4: no new variables on left side of :=

Use plain = to assign to a variable that already exists.

Unused Variables Are Errors

A local variable that is declared but never read stops the build:

./main.go:9:2: declared and not used: x

It is not a warning you can switch off. Delete the variable, use it, or, while debugging, silence it with the blank identifier: _ = x. The blank identifier also discards return values you do not need: _, err := fmt.Println("hi").

Package-level variables and function parameters are exempt from this rule.

Scope and Shadowing

A variable exists from its declaration to the end of the enclosing block ({ ... }). A := in an inner block creates a new variable, even if one with the same name exists outside. The outer one is shadowed, not changed:

The program prints inside: 42 and then outside: 0. The := inside the if declared both count and err as new variables in the inner block, because neither existed there yet. It compiles cleanly and is a classic source of "my value disappeared" bugs.

The fix is to declare err first and assign with =:

var err error
count, err = strconv.Atoi(input)

go vet does not report shadowing by default. The separate shadow analyzer does, and linters such as golangci-lint can enable it.

Naming

  • Use camelCase: maxRetries, userID. Not max_retries.
  • Keep acronyms in one case: userID, httpClient, parseURL, not userId.
  • Short names for short scopes: i in a loop, r for a reader, err for an error. Longer, descriptive names for package-level variables.
  • A capitalized name at package level (var Timeout) is exported to other packages. See packages and imports.

Package-Level Variables

Variables declared outside functions live for the whole program and are initialized before main runs, in dependency order. They are convenient for configuration and caches, and they are shared by every goroutine. If more than one goroutine writes to one, protect it with a mutex or use sync/atomic, or you have a data race.

Prefer constants for values that never change. A const cannot be modified by accident and costs nothing at run time.

Frequently Asked Questions

What is the difference between var and := in Go?

:= declares and initializes a variable with an inferred type, and only works inside functions: count := 10. var works everywhere (including package level), lets you state the type, and can omit the initial value to get the zero value: var count int. Inside functions, idiomatic Go uses := when there is an initial value and var when you want the zero value.

What are zero values in Go?

Every variable declared without a value gets its type's zero value: 0 for numbers, false for bool, "" for string, and nil for pointers, slices, maps, channels, functions and interfaces. Structs get the zero value of each field. Go has no uninitialized variables.

How do I fix "declared and not used" in Go?

Go rejects a local variable that is assigned but never read. Use it, delete it, or, while debugging, assign it to the blank identifier with _ = x. Package-level variables and function parameters are exempt.

How do I declare a global variable in Go?

Use var at package level, outside any function: var counter int. The short form := is not allowed there. A capitalized name (var Counter int) is visible to other packages; a lowercase one is visible only in its own package. Guard package-level variables with a mutex if goroutines write to them.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED