skip to content

Usable Zero Values

Go has no uninitialized memory: every declaration yields a defined zero value, and good APIs make that value usable - which works for sync.Mutex and bytes.Buffer but stops at maps and channels.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

questions

4

What is a zero value in Go, and what are the zero values of int, string, bool and a pointer?

level: juniorimportance: must knowfreq 82%

answer

  1. no uninitialised memory in Go
  2. declaration always sets something
  3. zero, false, empty string
  4. nil for the six nilable kinds
  5. structs and arrays zeroed part by part

basics

~20 s

Go sets every variable declared without an initializer to its type's zero value, so there is never uninitialised memory. That is 0 for numeric types, an empty string for string, false for bool, and nil for pointers, slices, maps, channels, functions and interfaces.

solid answer

~50 s

Go guarantees that a variable declared with `var` and no initializer is not garbage: it is set to its type's zero value. Numeric types get `0`, `bool` gets `false`, and `string` gets `""` — an empty string, not nil, because a string cannot be nil. Pointers, slices, maps, channels, function values and interfaces get `nil`. Composite types are zeroed recursively: every field of a struct and every element of an array is set to its own zero value, so `var s Server` is a real, fully formed struct value rather than a nil pointer. The practical payoff is that many types are useful straight after declaration — `var mu sync.Mutex` is an unlocked mutex — and you never need a constructor just to blank fields out. The cost is that a zero field is indistinguishable from one a caller deliberately set to zero.

code

go · 10 lines
go
var (
	n int
	s string
	b bool
	p *int
	m map[string]int
)

fmt.Println(n, s == "", b, p == nil, m == nil)
// 0 true false true true

go deeper

for a junior

Be ready to state the zero value for the common types on demand — 0, false, empty string, nil — and to say plainly that Go never leaves a declared variable holding random memory.

for a middle

Explain that zeroing is recursive through structs and arrays, that strings and numbers are not nilable, and why the guarantee lets many types skip a constructor entirely.

for a senior

Show where the guarantee costs you information: a zero field cannot be told apart from a deliberately zero one, so document what zero means for every exported field you ship.

for a principal

Own the convention across the codebase: decide whether zero means default or off in your packages' APIs, and keep the answer consistent so callers do not have to read each package's source to find out.

## The guarantee When memory is allocated for a variable in Go — by a `var` declaration, by a `new` allocation, by a composite value, or for a function's named results — and no explicit initialisation is supplied, the variable is set to the **zero value** for its type. This is a language-level guarantee, not a convention or a compiler optimisation. Go has no equivalent of C's uninitialised local, which holds whatever bytes happened to be on the stack. The zero value of every type is, at the machine level, all bits zero. That is why the guarantee is cheap: the runtime does not run per-variable initialisation code, it hands out memory that is already zeroed. ## The zero value per kind of type - **Numeric types** — `int`, `int8`…`int64`, the `uint` family, `float32`, `float64`, `complex64`, `complex128`, and the aliases `byte` and `rune`: all zero. `var n int` is `0`; `var f float64` is `0`. - **`bool`** — `false`. - **`string`** — the empty string, written `""`. A string is **not** nilable, so `s == nil` does not even compile. The tests for an empty string are `s == ""` or `len(s) == 0`. - **Pointers** (`*T`), **slices**, **maps**, **channels**, **function values** and **interfaces** — `nil`. These six kinds are the only ones in the language whose zero value is spelled `nil`. They do not all behave the same way afterwards, which is a topic of its own; what matters here is only that declaration leaves each of them nil. - **Structs** — every field is set to its own zero value, recursively, including embedded structs. `var s Server` is a complete `Server` value sitting in memory, not a pointer to one. - **Arrays** — every element is set to its own zero value. `var a [3]int` is `[0 0 0]`; `var b [2]string` is two empty strings. Note the contrast with a slice: an array has real, zeroed elements, while a nil slice has no backing array at all. ## Where the guarantee applies It applies to local variables, to package-level variables, to struct fields, to array and slice elements created by allocation, and to a function's named result parameters. Package-level variables are zeroed before any package initialisation runs, so an `init` function always sees zeroed globals unless an earlier initialiser has already assigned to them. It does **not** magically apply to values you never allocate: a nil pointer does not point at a zeroed struct, it points at nothing, and dereferencing it panics. ## Why the language works this way Two reasons, one about safety and one about API design. The safety reason is the obvious one: a whole class of C bugs — reading a local before writing it, forgetting one field in a struct initialisation — cannot happen. Every read of a declared variable returns a defined value. The design reason is more Go-specific. Because zeroing is guaranteed and recursive, a library author can choose the layout of a type so that the all-zero state is a **valid, ready-to-use** state. `sync.Mutex` does this: zero means unlocked, so `var mu sync.Mutex` needs no constructor. `bytes.Buffer` does this: zero means an empty buffer, because its internal byte slice is nil and writing to a nil slice appends into a fresh one. That is why Go code declares far fewer constructors than an equivalent Java or C# codebase, and why a struct field of type `sync.Mutex` is written as a plain field rather than built in a `New` function. ## The trade-off you inherit The guarantee also removes information. Go has no field default values and no notion of an absent field: `Config{}` and `Config{Timeout: 0}` are the same value, bit for bit. So a library that treats `0` as "use the default" makes it impossible for a caller to ask for a genuine zero, and a library that treats `0` as a real setting will silently apply it to callers who simply forgot the field. Choosing and documenting what zero means for each exported field is a real part of designing a Go API. ## Quick mental model A Go declaration is a promise that the memory is blank in a defined way. Numbers and booleans get their arithmetic identity, strings get emptiness, the nilable kinds get nil, and anything built out of those gets the same treatment applied to each part.

  • Is a Go string's zero value the same as nil?
    No. A `string` cannot be nil at all — `s == nil` is a compile error. Its zero value is the empty string, so `s == ""` and `len(s) == 0` are both true. Only pointers, slices, maps, channels, function values and interfaces have nil as their zero value; strings, numbers, bools, arrays and structs do not.
  • What does `var a [3]int` contain immediately after declaration?
    Three `int` elements, all `0`. An array is a value with its elements stored inline, so zeroing the array zeroes each element. Contrast `var s []int`, which is a nil slice with no backing array allocated at all — both report `len` of 0, but only the array actually holds three zeros you can index.
  • Do package-level variables get zero values too?
    Yes. Package-level `var` declarations without initializers are zeroed before any package initialisation runs, so initializer expressions and `init` functions always start from defined values. The mechanism is the same: the memory is handed out already zeroed rather than each variable being assigned by generated code.

A Go declaration hands you a blank form rather than a random sheet of paper: every box already has a defined default printed in it, so you can read any box before you fill it in.

saying these in an interview costs you the question

  • Says an unassigned Go variable holds garbage as in C
  • Claims the zero value of a string is nil
  • Thinks var s Server gives a nil pointer, not a struct
  • Believes every type needs a constructor before first use
  • Assumes zeroing applies only to local variables
open as a page

In Go, what does `var s Server` give you when Server holds an int, a nested struct, a [4]byte array and a map field?

level: middleimportance: must knowfreq 58%

basics

~20 s

You get a complete Server value with every field zeroed recursively: the int is 0, each field of the nested struct is its own zero value, all four array bytes are 0, and the map field is nil with nothing allocated for it.

open as a page

Your Go client library takes a Config with a Timeout time.Duration — how do you tell 'unset' from an explicit zero?

level: seniorimportance: should knowfreq 44%

basics

~20 s

From the value alone you cannot: Go zeroes every field, so an omitted Timeout and one explicitly set to 0 are identical bits. Either define zero to mean 'use the default', or make the field a pointer, or accept option functions.

open as a page

Why does `var buf bytes.Buffer` work with no constructor, and which other standard library types are usable at their zero value?

level: middleimportance: nice to knowfreq 40%

basics

~20 s

Because a bytes.Buffer's all-zero state is already a valid empty buffer: its internal byte slice is nil and writes append into a fresh one. sync.Mutex, sync.RWMutex, sync.WaitGroup, sync.Once, sync.Map, strings.Builder and http.Client are usable at zero too.

open as a page