Zero Values and Initialization
Overview
Every variable in Go has a value from the moment it’s declared. If you don’t explicitly initialize a variable, it gets a zero value—a sensible default that prevents undefined behavior.
Zero Values by Type
| Type | Zero Value |
|---|---|
bool |
false |
int, int8, … |
0 |
uint, uint8, … |
0 |
float32, float64 |
0.0 |
complex64, complex128 |
(0+0i) |
string |
"" (empty string) |
pointer |
nil |
slice |
nil |
map |
nil |
channel |
nil |
function |
nil |
interface |
nil |
Demonstrating Zero Values
package main
import "fmt"
func main() {
var b bool
var i int
var f float64
var s string
var p *int
var sl []int
var m map[string]int
var ch chan int
var fn func()
var iface interface{}
fmt.Printf("bool: %v\n", b) // false
fmt.Printf("int: %v\n", i) // 0
fmt.Printf("float64: %v\n", f) // 0
fmt.Printf("string: %q\n", s) // ""
fmt.Printf("pointer: %v\n", p) // <nil>
fmt.Printf("slice: %v\n", sl) // []
fmt.Printf("map: %v\n", m) // map[]
fmt.Printf("channel: %v\n", ch) // <nil>
fmt.Printf("func: %v\n", fn) // <nil>
fmt.Printf("interface: %v\n", iface) // <nil>
}Struct Zero Values
Struct fields get their respective zero values:
type User struct {
Name string
Age int
Active bool
Balance float64
}
var user User
// user.Name = ""
// user.Age = 0
// user.Active = false
// user.Balance = 0.0Nested Structs
type Address struct {
Street string
City string
}
type Person struct {
Name string
Address Address // Embedded struct gets zero values too
}
var p Person
// p.Name = ""
// p.Address.Street = ""
// p.Address.City = ""Array Zero Values
Arrays are initialized with zero values for each element:
var arr [5]int // [0, 0, 0, 0, 0]
var strs [3]string // ["", "", ""]
var bools [2]bool // [false, false]Why Zero Values Matter
1. Eliminates Undefined Behavior
// In Go, this is safe and predictable
var count int
count++ // count is now 1
// In C, this would be undefined behavior
// int count;
// count++; // Undefined! Could be anything2. Reduces Boilerplate
// No need for explicit initialization
type Config struct {
Debug bool
Timeout int
Host string
}
cfg := Config{} // All zeros, ready to use selectively
cfg.Host = "localhost" // Only set what differs from zero3. Enables “Usable Zero Value” Pattern
Well-designed types work correctly with zero values:
import "bytes"
// bytes.Buffer works without initialization
var buf bytes.Buffer
buf.WriteString("Hello, ")
buf.WriteString("World!")
fmt.Println(buf.String()) // "Hello, World!"
// sync.Mutex works without initialization
var mu sync.Mutex
mu.Lock()
mu.Unlock()Initialization Methods
Short Variable Declaration
name := "Alice" // Inferred type: string
count := 42 // Inferred type: int
price := 19.99 // Inferred type: float64Explicit Type Declaration
var name string = "Alice"
var count int = 42
var active bool = trueType Inference (var)
var name = "Alice" // Type inferred from value
var count = 42 // intMultiple Variable Declaration
var (
name = "Alice"
age = 30
active = true
)
// Or inline
x, y, z := 1, 2, 3Composite Literal Initialization
// Struct
user := User{
Name: "Alice",
Age: 30,
Active: true,
}
// Partially initialized (rest are zero values)
user := User{Name: "Bob"} // Age: 0, Active: false
// Array
arr := [3]int{1, 2, 3}
// Slice
sl := []string{"a", "b", "c"}
// Map
m := map[string]int{"one": 1, "two": 2}nil vs Zero Value
nil is the zero value for pointers, slices, maps, channels, functions, and interfaces.
nil Slice vs Empty Slice
var nilSlice []int // nil slice
emptySlice := []int{} // empty slice (not nil)
nilSlice == nil // true
emptySlice == nil // false
len(nilSlice) // 0
len(emptySlice) // 0
// Both work with append
nilSlice = append(nilSlice, 1) // [1]
emptySlice = append(emptySlice, 1) // [1]nil Map Danger
var m map[string]int // nil map
// Reading is safe
val := m["key"] // Returns 0 (zero value)
// Writing panics!
m["key"] = 42 // panic: assignment to nil map
// Always initialize before writing
m = make(map[string]int)
m["key"] = 42 // Now safeThe new vs make Functions
new(T)
Allocates zeroed memory and returns a pointer:
ptr := new(int) // *int pointing to 0
*ptr = 42
user := new(User) // *User with zero-valued fields
user.Name = "Alice"make(T, args)
Creates and initializes slices, maps, and channels:
// Slice with length and capacity
sl := make([]int, 5) // [0, 0, 0, 0, 0]
sl := make([]int, 0, 10) // [] with capacity 10
// Map
m := make(map[string]int) // Empty, ready to use
// Channel
ch := make(chan int) // Unbuffered channel
ch := make(chan int, 10) // Buffered channelSummary
| Concept | Description |
|---|---|
| Zero Value | Default value assigned to uninitialized variables |
| Purpose | Eliminates undefined behavior, reduces boilerplate |
new(T) |
Allocates zeroed memory, returns *T |
make(T) |
Creates initialized slice, map, or channel |
nil |
Zero value for pointers, slices, maps, channels, funcs, interfaces |
More examples
Example: zero values of common types
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func main() {
var n int
var s string
var b bool
var p *int
fmt.Printf("int=%d string=%q bool=%v ptr=%v\n", n, s, b, p)
}Expected:
int=0 string="" bool=false ptr=<nil>
Example: nil map vs empty map
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func main() {
var nilMap map[string]int
empty := map[string]int{}
fmt.Println("nil len:", len(nilMap), "read:", nilMap["x"])
fmt.Println("empty len:", len(empty), "read:", empty["x"])
empty["x"] = 1
fmt.Println("after write empty:", empty)
// nilMap["x"] = 1 // would panic
fmt.Println("nil map needs make before write")
}Expected:
nil len: 0 read: 0
empty len: 0 read: 0
after write empty: map[x:1]
nil map needs make before write
Runnable example
Complete program that prints zero values, shows a usable zero bytes.Buffer, and panics safely-avoided on a nil map.
Save as main.go. From an empty directory:
go mod init example
go run .package main
import (
"bytes"
"fmt"
)
type User struct {
Name string
Age int
Active bool
}
func main() {
var b bool
var i int
var f float64
var s string
var p *int
var sl []int
var m map[string]int
var ch chan int
var fn func()
var iface any
fmt.Printf("bool=%v int=%v float64=%v string=%q\n", b, i, f, s)
fmt.Printf("pointer=%v slice=%v map=%v chan=%v func=%v iface=%v\n", p, sl, m, ch, fn, iface)
var user User
fmt.Printf("user zero: %+v\n", user)
var arr [3]int
fmt.Printf("array zero: %v\n", arr)
// Usable zero value: no constructor required.
var buf bytes.Buffer
buf.WriteString("Hello, ")
buf.WriteString("zero values!")
fmt.Println(buf.String())
// nil map: read OK, write needs make
fmt.Println("nil map read:", m["missing"])
m = make(map[string]int)
m["ok"] = 1
fmt.Println("after make:", m)
// nil vs empty slice
var nilSlice []int
empty := []int{}
fmt.Printf("nilSlice==nil? %v empty==nil? %v\n", nilSlice == nil, empty == nil)
nilSlice = append(nilSlice, 7)
fmt.Println("append on nil slice:", nilSlice)
}Expected output (illustrative):
bool=false int=0 float64=0 string=""
pointer=<nil> slice=[] map=map[] chan=<nil> func=<nil> iface=<nil>
user zero: {Name: Age:0 Active:false}
array zero: [0 0 0]
Hello, zero values!
nil map read: 0
after make: map[ok:1]
nilSlice==nil? true empty==nil? false
append on nil slice: [7]
What to notice: - Every type has a defined zero—no “undefined” locals. - Struct fields zero independently; arrays zero every element. - bytes.Buffer works from a zero value (idiomatic design). - Writing a nil map would panic; make first is required.
Try next: Comment out m = make(...) and assign m["ok"] = 1 to observe the panic, then restore it.