When I first started learning Go, I discovered structs before I discovered maps.
So naturally...
Everything became a struct.
Need a user?
type User struct {
Name string
Email string
}
Easy.
Need application settings?
type Settings struct {
Theme string
Notifications bool
}
Still easy.
Need categories users can create themselves?
...
I still made a struct.
type Categories struct {
Fitness string
Reading string
Coding string
}
It looked fine until someone wanted to add "Meditation."
Then another user wanted "Gaming."
Then another wanted "Cooking."
That's when it hit me.
Structs aren't meant to grow while your program is running.
I was using the wrong tool.
What a struct actually is
A beginner usually hears:
"A struct groups related data."
That's true.
But it misses the important part.
A better definition is:
A struct describes something whose shape is already known.
For example, every user in your application has the same fields.
type User struct {
Name string
Email string
Age int
}
You already know those fields before writing the program.
That's why a struct exists.
Think of it as a blueprint.
Every house built from the same blueprint has the same rooms.
What a map actually is
A map is completely different.
A map doesn't care what keys you'll use tomorrow.
prices := map[string]float64{
"apple": 0.50,
}
Later...
prices["banana"] = 0.30
prices["mango"] = 1.20
prices["orange"] = 0.75
No problem.
Maps are designed for data whose keys are discovered at runtime.
The question that changed how I write Go
Nowadays, before writing any type, I ask myself one question.
Do I already know every field this data will have?
If the answer is yes, I create a struct.
If the answer is:
"Well...the user decides."
or
"It depends on the API."
or
"It depends on the environment."
I immediately reach for a map.
That one question has saved me countless rewrites.
⚠️ Beginner Mistake #1: Treating dynamic data like fixed data
Imagine you're storing HTTP headers.
A beginner might write this.
type Headers struct {
ContentType string
Authorization string
UserAgent string
}
Looks reasonable...
Until a request contains:
X-Request-IDX-Forwarded-For- Or some completely custom header.
Now your program needs another field.
And another.
And another.
Instead, use a map.
headers := map[string]string{
"Content-Type": "application/json",
}
Now any header can exist without changing your code.
That's exactly what maps were made for.
⚠️ Beginner Mistake #2: Using maps because they feel flexible
Then comes the opposite mistake.
You discover maps.
They're cool.
They're flexible.
So suddenly everything becomes
map[string]interface{}
Even users.
user := map[string]interface{}{
"name": "Perez",
"age": 22,
}
This works.
But you've thrown away one of Go's biggest strengths.
Type safety.
This typo compiles.
fmt.Println(user["nmae"])
Notice the spelling?
The compiler doesn't.
It happily runs your program.
Now compare that with
type User struct {
Name string
}
fmt.Println(user.Nmae)
Go immediately says:
"Nope."
Compilation fails.
That's exactly what you want.
Let the compiler catch mistakes before your users do.
Structs and Maps Are Teammates, Not Enemies
One thing took me embarrassingly long to realize.
Structs and maps aren't competing.
They're usually used together.
A very common pattern is
type Product struct {
Name string
Price float64
}
inventory := map[string]Product{}
Notice what's happening.
The Product has a known shape.
The inventory doesn't know how many products exist.
One models an object.
The other stores a growing collection of those objects.
You'll see this pattern everywhere.
- User sessions
- Product inventories
- Cache systems
- Feature flags
- Configuration
- Backend services
Once you notice it, you can't unsee it.
Struct vs Map: A Quick Decision Guide
| Situation | Struct | Map |
|---|---|---|
| Known fields | ✅ | |
| Unknown keys | ✅ | |
| Type safety | ✅ | |
| Dynamic user input | ✅ | |
| Real-world objects | ✅ | |
| Fast lookups by key | ✅ |
My Rule of Thumb
Whenever I'm unsure, I ask myself:
Can I confidently write every field name before running the program?
If yes...
➡️ Use a struct.
If no...
➡️ Use a map.
Simple.
Final Thoughts
One of my favorite things about Go is that it gives you only a handful of data structures.
That means learning when to use them matters more than memorizing how to use them.
I spent weeks treating every problem like a struct.
Then I spent weeks treating every problem like a map.
The sweet spot was realizing they solve completely different problems.
A struct answers:
"What is this thing?"
A map answers:
"How do I find this thing?"
Once that clicked, my Go code became much simpler.
Over to You
What beginner Go mistake took you the longest to understand?
I'd love to hear it in the comments. Chances are, someone else is struggling with the exact same thing.
If you enjoyed this article, consider following the Golang Beginner Mistakes series, where we break down the kinds of mistakes almost every new Go developer makes—and how to avoid them.
Top comments (0)