Zero Value of Structs
Understanding how Go initializes struct fields to their zero values, why it matters, and how to use it safely.
A struct in Go does not arrive as a raw, garbage-filled block of memory. Declaring a struct variable without explicit initialization does not leave its fields dangling — Go automatically sets every field to the zero value for its type. The existing note in this section captures the core idea: all fields are set to their own zero values. This page expands that into the full picture of what zero values are, why Go chose them, how they behave inside a struct, and where they help or hurt in real code.
What a Zero Value Struct Is
A zero value struct is a struct variable where every field holds the type’s default, pre‑defined zero. The struct itself is a valid, usable value — it is not nil. For a struct variable declared with var, the compiler guarantees that each field is initialized:
- Numeric types (
int,float64, etc.) →0or0.0 string→""(the empty string)bool→false- Pointers, slices, maps, channels, interfaces, and functions →
nil - Arrays → every element is zeroed (the array itself is not nil)
- Nested structs → each of their fields is recursively zeroed
The struct as a whole is simply the aggregation of these individual zero values. There is no hidden “uninitialized” flag.
Why Go Initializes Structs to Zero
Many languages leave variables uninitialized — reading an uninitialised int in C or C++ returns whatever bits happened to sit at that memory location. That is fast but dangerous, and it creates a universe of subtle bugs. On the other end of the spectrum, languages like Rust force the programmer to initialize every value before it can be read, shifting the burden to the compiler’s static analysis.
Go takes a middle path that fits its design philosophy: memory is always zeroed before a value is handed to the program. This is not an accident; it keeps the language safe and the compiler simple. The runtime can just memset a block of memory to zero bytes, and the result is guaranteed to be valid for every Go type — because Go’s zero values are deliberately chosen so that a sequence of zero bytes maps directly to the type’s default state. For a garbage-collected language where pointers must never dangle, this approach also keeps the garbage collector sane: a zero‑initialized pointer is nil, not an arbitrary address.
A beginner can think of a zero value struct as a blank form: every field is present, but nothing has been filled in yet. The form is real and you can hand it to someone — you just haven’t written anything on it.
How Struct Fields Get Zeroed
When you declare var s MyStruct, the compiler allocates space for the struct and the runtime ensures that space is zeroed. The same happens when you use new(MyStruct) (which returns a pointer to a zeroed struct) or write the empty composite literal MyStruct{}. All three paths produce the same underlying zero value.
The zeroing follows the type of each field recursively. This example shows a struct with embedded types to demonstrate the depth:
package main
import "fmt"
type Address struct {
City string
ZipCode int
}
type Person struct {
Name string
Age int
Active bool
Home Address
Aliases []string
Scores map[string]int
}
func main() {
var p Person
fmt.Printf("%#v\n", p)
}
Running this prints something like:
main.Person{Name:"", Age:0, Active:false, Home:main.Address{City:"", ZipCode:0}, Aliases:[]string(nil), Scores:map[string]int(nil)}
Notice that Aliases is a nil slice and Scores is a nil map. The struct’s numeric and string fields are zero, the nested Home struct is itself a zero value struct, and reference types are nil. Everything is predictable.
Expected output:
If your program prints zero values in each field exactly as shown above, your Go environment is working correctly. The zeroing guarantee is part of the language specification, so you can rely on it across platforms.
Creating a Zero Value Struct
There are two common ways to obtain a zero value struct — both produce identical values:
var p1 Person // var declaration
p2 := Person{} // empty composite literal
fmt.Println(p1 == p2) // true
The var declaration is the most explicit signal that you want the zero value. The empty composite literal Person{} is often used inline or when you want to override only a few fields using named keys (omitted fields keep their zero values). Both forms work on value types. To get a pointer to a zero value struct, you can use:
p3 := new(Person) // pointer to zero value
// or
p4 := &Person{} // also pointer to zero value
These two are equivalent; p3 and p4 both point to a struct where every field is zeroed.
Reference-Type Fields Inside a Zero Value Struct
The fields that cause the most confusion are slices, maps, and channels. In a zero value struct, these are nil. A nil slice can be used safely in many contexts (len, append, range), but a nil map cannot be written to — attempting to add a key will panic.
type Config struct {
Options map[string]string
}
func main() {
var c Config
// This line will panic with: assignment to entry in nil map
c.Options["timeout"] = "30s"
}
Runtime panic:
A nil map inside a zero value struct will cause a runtime panic if you try to store a key. Always initialize map fields with make before writing to them, or use a composite literal that provides the map at construction time.
To avoid this, you have two straightforward options:
- Initialize the map at construction:
c := Config{Options: make(map[string]string)}. - Or, after declaring the zero value struct, assign a new map:
c.Options = make(map[string]string).
Slices and channels are less dangerous; a nil channel blocks forever on send/receive, which can be useful in select statements, but attempting to send on a nil channel inside a zero value struct without intent is still a bug. The core lesson is that the struct’s zero value does not recursively initialize reference types to usable empty instances — it sets them to nil.
How Beginners Should Think About Zero Value Structs
Treat a zero value struct as a ready‑to‑use, empty container. It is safe to pass around, compare, and inspect. The only time you need to be careful is when the struct contains maps or other nil reference types that require explicit initialization before mutating them. A mental model that helps: the struct is like a new notebook — every page exists, but nothing is written on any page. You can read an empty page (zero value), but if you want to store a map inside the notebook, you first need to insert a blank map page (via make).
Common Mistakes and Misconceptions
Confusing zero value with “uninitialized.” A zero value struct is fully initialized; you just haven’t set meaningful data yet. This is fundamentally different from languages where reading an uninitialized variable is undefined behavior.
Assuming the struct is nil. A value of a struct type is never nil. Only a pointer to a struct can be nil. The zero value struct is a valid, non‑nil value with all fields zeroed.
Using a nil map field without initialization. As shown earlier, this panics. The zero value struct gives you a nil map, not an empty map. That is a common source of bugs when a struct contains a map for optional configuration.
Misinterpreting valid zero‑valued fields as “not set.” Sometimes 0 or "" is a perfectly meaningful value (a point at the origin, an empty name, etc.). When you need to distinguish between “the field was intentionally left as zero” and “the field wasn’t provided,” you must use a different approach.
Nil map pitfall:
A struct field of type map[string]int is nil in the zero value struct. Reading from it is fine (it behaves like an empty map and returns the zero value for missing keys), but writing to it will panic. Always check for nil or initialize the map before mutations.
Practical Uses of Zero Value Structs
- Default configuration. Many packages export a struct type whose zero value represents sensible defaults. Calling code can start with
var opts MyOptionsand then override only the fields they care about. - Partial initialization. When using a struct literal with named fields, omitted fields keep their zero values. This lets you create a struct with only the fields you want to set.
- Deserialization. When
json.Unmarshalfills a struct, missing JSON keys leave the corresponding struct fields untouched. If you start with a zero value struct, missing keys translate to zero values. - Equality checks. The zero value of a comparable struct serves as a natural “empty” value for comparisons (
if cfg == Config{}).
Design intention:
The Go designers explicitly chose zero values so that a freshly allocated value is immediately usable without a constructor. This simplifies APIs and reduces boilerplate, but it also shifts the responsibility to you to handle reference-type fields that start nil.
Summary
Zero value structs are not a quirk; they are a deliberate part of Go’s memory model. Every struct field is initialized to a predictable zero based on its type. This gives you safety, avoids uninitialized memory bugs, and keeps the language simpler than alternatives that require explicit initialization checks.
The most important insight: a zero value struct is a fully valid value, but any nil reference fields inside it (maps, slices, channels) require explicit initialization before mutation. If your struct holds only value types or nil‑safe reference types (like slices), the zero value is immediately useful. If it contains maps, plan to make them early. Understanding this distinction is what turns a potential panic into a reliable default.