AlgoMaster Logo

Zero Values

Last Updated: May 22, 2026

High Priority
7 min read

When you declare a variable in Go without giving it a value, Go doesn't leave it holding random memory garbage. It sets it to a predictable starting value called the zero value. This rule applies to every type, every time, with no exceptions, and it shapes how Go programs are written.

What a Zero Value Is

A zero value is the default value Go assigns to a variable that's declared but not explicitly initialized. The compiler guarantees this. There is no "uninitialized" state in Go.

The line for customerName ends with nothing after the colon. That's the zero value of a string, which is the empty string "". It prints as nothing, but it's a real, valid string value. You can take its length (0), pass it to functions, and concatenate it with other strings. It just happens to contain no characters.

This is different from many other systems where reading an uninitialized variable is undefined behavior. In Go, var stockCount int is guaranteed to give you 0, every single time, on every platform.

Zero Values for Each Type

Every type in Go has exactly one zero value. Here's the full set you'll meet in everyday code:

TypeZero Value
int, int8, int16, int32, int640
uint, uint8, uint16, uint32, uint64, byte0
float32, float640
boolfalse
string"" (empty string)
Pointer (*T)nil
Interfacenil
Slice ([]T)nil
Map (map[K]V)nil
Channel (chan T)nil
Functionnil
StructAll fields set to their own zero values
Array ([N]T)Every element set to its zero value

Three things stand out from this table. First, all numeric types share the same zero value, 0. Second, all the "reference-like" types (slice, map, channel, pointer, interface, function) have nil as their zero value. Third, composite types (struct, array) get zero-valued recursively, every field or element ends up at its own zero value.

Why Zero Values Exist

The zero-value rule is one of Go's deliberate design decisions, not a side effect. It exists for three reasons.

No uninitialized state. A variable is either declared (and zero-valued) or not declared at all. There's no third option. This eliminates an entire family of bugs where code reads garbage from memory.

No undefined behavior. In languages that allow reading uninitialized memory, the result can be anything: yesterday's data, security-sensitive bytes, a value that happens to look reasonable in testing and crashes in production. Go takes this off the table.

Types are useful from creation. Many Go types are designed so that the zero value is immediately usable. You don't need a constructor, an init() call, or a factory function. You declare the variable and start using it.

That last point is the one with the biggest practical impact, so it's worth looking at concrete examples.

The Zero Value Is Often Usable

A good Go type makes its zero value do something sensible. Two examples from the standard library show this off well.

bytes.Buffer is a growable buffer of bytes. Its zero value is an empty buffer, ready to write to:

No bytes.NewBuffer(...) call, no setup. The var declaration gives you a working buffer.

sync.Mutex is the same story. Its zero value is an unlocked mutex, ready to lock:

You'll see this pattern often in Go code: declare a variable with var and start using it. It's idiomatic, and it works because the authors of those types thought carefully about what the zero value should be.

Slices are another good case. A nil slice is the zero value of []T, but you can range over it, take its length, and even append to it:

Ranging over a nil slice runs the loop zero times, no panic. append to a nil slice works fine, returning a fresh slice with a backing array. This is intentional. Code that processes a list of orders can treat "no orders" and "some orders" identically without a special-case check.

Zero Values in Structs

When you declare a struct without initializing its fields, every field gets its own zero value. The struct itself is fully formed and usable, you just have to know which fields are still at their defaults.

Every field is at its zero value: 0 for the numerics, empty string for the text fields, false for the bool. The %+v verb prints field names alongside values, which makes it easy to see this. Without it, the output would just be {0 false 0}, and the empty strings would be invisible.

This recursive zeroing applies to nested structs too. If a struct contains another struct, that inner struct also gets zero-valued field by field. Same for arrays inside structs: every element starts at the element type's zero value.

Zero Values in Arrays

Arrays in Go have a fixed length, and every element is zero-valued when you declare the array:

The topProducts line prints five empty strings separated by spaces, which looks almost empty. The len shows there really are five slots, all holding "".

This makes arrays predictable: a [100]int is a hundred zeros, every single time. No "did I remember to initialize it" question.

Using Zero Values as Starting Points

A common Go pattern is to declare a variable at its zero value and build it up from there. This works because numeric zeroes, empty strings, false bools, and nil slices/maps are all natural starting points for accumulation.

Summing a cart total:

Counting items in stock:

Building a list of names:

In all three cases, the loop starts with the zero value (0, 0, or nil) and adds to it. No "initialize to zero" line needed, the var declaration already did that.

How Zero Values Flow Through a Program

A useful way to picture this: every place a variable comes into existence, Go fills it with the zero value first. This is true for declared variables, struct fields, array elements, and even the receiving variables in multi-value returns.

The diagram shows the main entry points: a var line, an uninitialized field in a struct, an element of an array, or a slot in a slice created by make. All of them go through the same step, the zero value is applied, and the variable is ready to use.

This consistency is what makes Go code easy to reason about. You never have to ask "is this variable initialized yet?" because the answer is always yes.

The Nil Map Gotcha

There's one important exception to "zero values are usable".

A nil map (the zero value of a map) can be read from, but not written to. Reading from it gives you the value type's zero value. Writing to it panics at runtime.

The read on line 8 works because Go is happy to tell you "this key isn't present, here's the zero value". The write on line 11 has nowhere to put the value, the map has no backing storage, and the program crashes.

The fix is to initialize the map with make before writing to it:

Slices and maps both have nil as their zero value, but they behave differently when you try to write. append to a nil slice works fine (it allocates for you). Writing to a nil map does not. The reason is that append returns a new slice header (so it can replace the nil one), but assigning to a map key has nowhere to put that "new header" back, so the language doesn't allow it.

Zero Values for Reference-Like Types

Pointers, interfaces, slices, maps, channels, and functions all have nil as their zero value. Each one means something slightly different:

Notice that nil slices print as [] and nil maps as map[], while nil pointers, channels, and functions all print as <nil>. They are all genuinely nil, the difference is just in how fmt chooses to display them.

What you can do with each:

  • nil pointer: Cannot be dereferenced (*orderPtr panics). Can be compared to nil. Can be assigned to.
  • nil slice: Can be read (len, cap, range, indexing returns the panic only if out of bounds, which it always is). Can be appended to.
  • nil map: Can be read. Cannot be written to.
  • nil channel: Sends and receives block forever. Useful trick in select statements.
  • nil function: Calling it panics. Can be compared to nil to check before calling.

nil is a real, normal value in Go, not an error. Many operations work on nil values. A few don't.

Reading vs Writing Nil Maps Side by Side

A nil map answers reads with the zero value of the value type, and returns 0 for len and loops zero times for range. But the moment you try to assign to a key, the runtime stops the program.

Bringing It Together

The following program uses zero values in several ways at once: a shopping cart with a running total, an item list built up from nothing, and a flag tracking whether a discount has been applied.

Every one of those var lines relies on the zero value as its starting point: 0.0, 0, false, and nil. Nothing is initialized explicitly, and the program still works correctly because Go gives you sensible defaults for every type.