Why Go Doesn't Let Variables Stay Uninitialized
In many languages, a new variable can be empty in a dangerous way: “I exist, but nobody put anything in me yet.” Read that variable and you might get random junk from memory.
Go says no to that.
Every variable starts with a real value. Either you set it — or Go sets a safe default called the zero value.
Forget “what is int?” for a second. The sticky question is:
Why does every variable already have a value — and how does that stop bugs?
Also read: Why Simple Code Beats Clever Code and Why Google Invented Go.
Have you been bitten by an uninitialized variable in another language?
---
Remember this one line
If a variable exists in Go, it already has a value.
No ghosts. No “maybe garbage.” No surprise crashes from unread memory.
var, types, const, and := all live under that rule.
---
Variables, types, constants, zeros, and :=
1 of 5
Variables
A variable is a labeled box. In Go, the box is never empty when you create it — it always starts with something real.
How you write it. Write var count int, or var name = "Ada". If you give a value, Go can often guess the type for you.
Why it sticks. In C, a new local can hold leftover junk from memory. In Go, that never happens. Forget to set a number? You still get 0 — not a random crash later.
Don’t get tripped. A variable at package level lives for the whole program. Keep most variables short-lived inside functions. Shared globals are easy to regret.
Example
var count int var name string = "Ada" var ready = true
---
What each zero value really means
int starts at 0. Easy. The harder part is knowing which zeros are safe to use — and which ones blow up.
By type
What Go puts in the box first — and whether that starter is safe to use right away.
int / float64
→ 0 / 0.0Picture it. You start at nothing to add. Perfect for counters and totals.
Safe to use. Just write var sum int and start adding. You already begin at 0.
Watch out. Sometimes 0 means “none yet,” sometimes it means “really zero.” If both matter, don’t guess — use a flag or a pointer.
---
var, :=, and const
Same job, different tools.
Compare
var — clear and steady
var total int total = sum(items) var name string = "Ada"
:= — quick and local
total := sum(items) name := "Ada"
Have a value ready inside a function? Use :=. Want the zero first, or a package-level name? Use var.
---
Why this design choice exists
Two questions
The design choice underneath the syntax.
Why does every variable already have a value?
Because “I forgot to set this” is a classic bug — and Go won’t leave that trap in the language.
Why it helps. In older languages like C, a new variable can hold random leftover bits from memory. Sometimes it works. Sometimes it explodes. Those bugs are nightmare fuel. Go’s fix is simple: create a variable, get a known starter value. Always.
But wait. A known starter can still be the wrong answer for your problem. If 0 means both “unset” and “zero retries,” you’ve made a new mess — just a quieter one.
Zeros kill random garbage. They don’t kill fuzzy meaning. Use the zero when empty is a great start. When “empty” and “missing” differ, say missing clearly — with ok, a pointer, or a small status type.
---
In real code
Counting is free
var sum int
for _, n := range values {
sum += n
}You don’t write sum = 0. Zero is already the right place to start adding.
Sticky idea: Go’s default often matches the math you wanted.
Zero is not the same as “missing”
func port(cfg map[string]int) int {
if p, ok := cfg["port"]; ok {
return p
}
return 8080
}Is 0 a real port, or did someone forget to set one? If both are possible, don’t make 0 mean “I forgot.” Ask the map: “was this key there?”
Sticky idea: Empty ≠ missing. Say missing out loud in code.
Good types work on day zero
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()No NewMutex(). It just works. When you invent a type, ask: can someone write var t T and use it safely?
Sticky idea: The best Go types are ready before you “set them up.”
:= is fast — and sneaky
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
b, err := io.ReadAll(f) // same err, new b — allowed
if err != nil {
return err
}:= creates what’s new and reuses what’s old. Handy. Also easy to accidentally create a second err inside an if and wonder why the outer one never updates.
Sticky idea: Always glance at the left side of :=.
---
Try this
- Find a
varwith no=. Say its starting value out loud. Is that a good start? - Find a place where
0or""means “not set.” Fix it so “missing” is clear. - Build a tiny type that works with
var t T— no constructor needed. - Search for
:=insideif. Check you didn’t hideerrby accident. - Print a nil slice and an empty slice as JSON. Notice
nullvs[].
---
Take these home
- No empty ghosts — every variable starts with a value.
- Types are rules — they tell the compiler what you’re allowed to do.
constis frozen at build time — not a variable you promise not to change.:=for quick locals ·varwhen you want the zero (or package scope).- Zero stops random bugs — it does not stop “I used 0 to mean missing.”
- Design for the default — make
var t Tsafe when you can.
---
Picture a blank form that somehow still has random scribbles in the boxes. That’s uninitialized memory.
Go hands you a clean form every time — zeros filled in. Your job is simpler: write the real answers, and when “blank” and “missing” are different things, say so in the code.