Skip to main content

Variable Declaration

What is a Variable?​

A variable is a named memory location that stores a value during program execution. In Go, the type of a variable is determined at declaration and cannot change afterward. This is known as static typing.

Go's variable system has two key characteristics.

First, zero values: when you declare a variable without an initial value, Go automatically assigns the default value for that type. Unlike C, there are no garbage values.

Second, declared variables must be used: the Go compiler treats a declared-but-unused variable as a compile error. This is a deliberate design choice that enforces code quality and eliminates dead code.


Declaring Variables with var​

The most fundamental way to declare a variable in Go is with the var keyword.

package main

import "fmt"

func main() {
// 1. Specify both type and initial value
var name string = "Gopher"
var age int = 30
var score float64 = 98.5

// 2. Type only (initial value defaults to zero value)
var count int // 0
var isActive bool // false
var message string // ""

// 3. Initial value only (type inference β€” Go determines the type)
var pi = 3.14159 // inferred as float64
var greeting = "Hi" // inferred as string
var flag = true // inferred as bool

fmt.Println(name, age, score)
fmt.Println(count, isActive, message)
fmt.Println(pi, greeting, flag)
}

You can also use var at the package level (outside any function).

package main

import "fmt"

// Package-level variables (global variables)
var AppName = "MyApp"
var Version = "1.0.0"
var MaxConnections int = 100

func main() {
fmt.Printf("%s v%s (max connections: %d)\n", AppName, Version, MaxConnections)
}

var Block Declaration​

When declaring multiple variables at once, use a block form.

package main

import "fmt"

var (
host = "localhost"
port = 8080
debug = false
maxRetry = 3
)

func main() {
fmt.Printf("Server: %s:%d (debug=%v, maxRetry=%d)\n", host, port, debug, maxRetry)
}

Short Variable Declaration :=​

:= is a short declaration operator that can only be used inside functions. It omits the var keyword and type annotation, inferring the type from the initial value. This is the most commonly used declaration style in Go code.

package main

import "fmt"

func main() {
// := declares, infers type, and initializes all at once
name := "Gopher"
age := 30
score := 98.5
active := true

fmt.Println(name, age, score, active)

// Assign new values to existing variables (use =)
age = 31
score = 99.0
fmt.Println(name, age, score)
}

var vs := Comparison​

Aspectvar:=
ScopePackage level + inside functionsInside functions only
Explicit typeSupported (optional)Not supported (always inferred)
No initial valueSupported (uses zero value)Not supported (initial value required)
Code styleExplicitConcise
package main

import "fmt"

func main() {
// var β€” when you want to be explicit about type, or no initial value
var buffer []byte // nil slice
var result map[string]int // nil map
var err error // nil error

// := β€” quick declarations inside functions
message := "hello"
count := 0
items := []string{"a", "b", "c"}

fmt.Println(buffer, result, err)
fmt.Println(message, count, items)
}

Zero Values​

In Go, if you declare a variable without an initial value, it is automatically assigned the zero value for its type.

TypeZero Value
int, int8, int16, int32, int640
uint, uint8, uint16, uint32, uint640
float32, float640.0
complex64, complex128(0+0i)
boolfalse
string"" (empty string)
pointernil
slicenil
mapnil
channelnil
functionnil
interfacenil
package main

import "fmt"

func main() {
var i int
var f float64
var b bool
var s string
var p *int
var sl []int
var m map[string]int

fmt.Printf("int: %v\n", i)
fmt.Printf("float64: %v\n", f)
fmt.Printf("bool: %v\n", b)
fmt.Printf("string: %q\n", s) // %q shows the empty string clearly
fmt.Printf("pointer: %v\n", p)
fmt.Printf("slice: %v (nil: %v)\n", sl, sl == nil)
fmt.Printf("map: %v (nil: %v)\n", m, m == nil)
}

Output:

int:     0
float64: 0
bool: false
string: ""
pointer: <nil>
slice: [] (nil: true)
map: map[] (nil: true)

Multiple Variable Declaration and Assignment​

Go allows declaring or assigning multiple variables simultaneously on a single line.

package main

import "fmt"

func main() {
// Multiple declarations (same type)
var x, y, z int
fmt.Println(x, y, z) // 0 0 0

// Multiple declarations + initialization
var a, b, c = 1, 2, 3
fmt.Println(a, b, c) // 1 2 3

// := multiple declaration
name, age, score := "Alice", 25, 95.5
fmt.Println(name, age, score)

// Multiple assignment (swap pattern β€” the elegant Go way)
p, q := 10, 20
fmt.Println("before:", p, q) // before: 10 20
p, q = q, p // swap without a temporary variable!
fmt.Println("after:", p, q) // after: 20 10
}

Redeclaration with :=​

When using :=, even if some variables already exist, the statement is valid as long as at least one new variable is being declared.

package main

import "fmt"

func main() {
x := 1
fmt.Println(x)

// x already declared, but y is new β€” OK
x, y := 2, 3
fmt.Println(x, y)

// reassign with =
x, y = 10, 20
fmt.Println(x, y)
}

Variable Scope​

Go's variable scope is block ({}) based. An inner block can access variables from outer blocks, but not vice versa.

package main

import "fmt"

// Package-level variable
var globalVar = "I am global"

func main() {
// Function-level variable
funcVar := "I am in main"

if true {
// Block-level variable
blockVar := "I am in if block"
fmt.Println(globalVar) // accessible
fmt.Println(funcVar) // accessible
fmt.Println(blockVar) // accessible
}

// blockVar is not accessible here (compile error)
// fmt.Println(blockVar) // error!

for i := 0; i < 3; i++ {
loopVar := i * 2
fmt.Println(loopVar) // accessible
}
// i and loopVar are not accessible here
}

Beware of Shadowing​

Declaring a variable in an inner block with the same name as an outer variable causes shadowing. This can lead to subtle bugs if unintentional.

package main

import "fmt"

var value = 100 // package level

func main() {
fmt.Println(value) // 100

value := 200 // shadows the package-level variable!
fmt.Println(value) // 200

{
value := 300 // shadows again at block level!
fmt.Println(value) // 300
}

fmt.Println(value) // 200 (back to function-level value)
}

Unused Variables β€” Compile Error​

Go treats declared-but-unused variables as a compile error. This is a deliberate philosophical choice to enforce code quality.

package main

func main() {
x := 10
// If x is never used:
// ./main.go:4:2: x declared and not used
_ = x // assigning to blank identifier suppresses the error
}

Note: package-level variables do not trigger this error even if unused.


Blank Identifier _​

_ (underscore) is a special blank identifier used to discard values. Assigning to _ never causes a "declared and not used" error.

package main

import "fmt"

// Function returning multiple values
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}

func getCoordinates() (x, y, z float64) {
return 1.0, 2.5, 3.7
}

func main() {
// Ignore the error and only get the result (error handling is recommended in real code)
result, _ := divide(10.0, 3.0)
fmt.Printf("Result: %.4f\n", result)

// Ignore unneeded return values
x, _, z := getCoordinates()
fmt.Printf("x=%.1f, z=%.1f (y ignored)\n", x, z)
}

Real-World Example: Handling Multiple Return Values​

Go supports multiple return values. This is heavily used in error handling patterns.

package main

import (
"fmt"
"strconv"
)

// Convert a string to an integer, returning an error on failure
func parseAge(s string) (int, error) {
age, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("invalid age value %q: %w", s, err)
}
if age < 0 || age > 150 {
return 0, fmt.Errorf("age out of range: %d", age)
}
return age, nil
}

func main() {
inputs := []string{"25", "abc", "-5", "200", "42"}

for _, input := range inputs {
age, err := parseAge(input)
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("Age: %d\n", age)
}
}

Output:

Age: 25
Error: invalid age value "abc": strconv.Atoi: parsing "abc": invalid syntax
Error: age out of range: -5
Error: age out of range: 200
Age: 42

Ignoring Index or Value in Loops​

package main

import "fmt"

func main() {
fruits := []string{"apple", "banana", "cherry", "date"}

// Ignore index β€” only need values
fmt.Println("=== Fruit List ===")
for _, fruit := range fruits {
fmt.Println("-", fruit)
}

// Ignore value β€” only need indices
fmt.Println("\n=== Index List ===")
for i := range fruits {
fmt.Printf("index %d\n", i)
}

// Ignore value in map iteration (checking key existence)
seen := map[string]bool{
"go": true, "python": true, "rust": true,
}
languages := []string{"go", "java", "rust", "c++"}
for _, lang := range languages {
if _, exists := seen[lang]; exists {
fmt.Printf("%s: in study list\n", lang)
} else {
fmt.Printf("%s: not in study list\n", lang)
}
}
}

Pro Tips​

Tip 1: Short names belong in short scopes

Go community convention: use short names like i, v, k for short-lived loop variables. Use descriptive names when the scope is longer.

// Loop variable β€” short name is fine
for i, v := range items { ... }

// Package level β€” use a clear, descriptive name
var maxConnectionPoolSize = 100
var defaultRequestTimeout = 30

Tip 2: Use := when the initial value is obvious

// Good
conn, err := net.Dial("tcp", "localhost:8080")
defer conn.Close()

// Unnecessarily verbose
var conn net.Conn
var err error
conn, err = net.Dial("tcp", "localhost:8080")

Tip 3: Minimize package-level variables

Package-level variables create global state, making testing and maintenance harder. Prefer passing values as function arguments or struct fields.

Tip 4: Group related variables in a block declaration

var (
// DB configuration
dbHost = "localhost"
dbPort = 5432
dbName = "myapp"

// Server configuration
serverPort = 8080
serverTimeout = 30
)