When I first started learning Go, I thought I was doing everything right—until I ran into a weird bug about golang range loop reference. I was iterating over a list of Book
structs (of course, I can't share the real structs and code used here... all here are for turorial purpose), taking the pointer of each one, and storing them into a slice. But at the end of the loop, all the pointers pointed to... the same book?! 🤯
Let’s walk through this classic Go beginner mistake together — and fix it the right way.
📚 The Use Case: A Slice of Books in a Library
Suppose we have a list of books, and we want to collect pointers to each one so we can modify them later.
Here’s the code I thought would work:
for _, book := range books { bookPointers = append(bookPointers, &book) // Oops... }
But when I printed out the pointers, they all pointed to the last book in the list. This bug stumped me for a while until I understood one critical Go behavior.
The File Structure To Run The Code
learning-golang/ ├── 01-loop-reference-pitfall/ │ ├── main.go │ └── README.md ├── Makefile ├── bin/ └── go.mod
This is the complete buggy code:
package main import ( "fmt" ) type Book struct { Title string Author string } func main() { originalBooks := []Book{ {"Go in Action", "William Kennedy"}, {"The Go Programming Language", "Alan Donovan"}, {"Introducing Go", "Caleb Doxsey"}, } fmt.Println("❌ Buggy Version:") var buggyPointers []*Book for _, book := range originalBooks { buggyPointers = append(buggyPointers, &book) } for _, bp := range buggyPointers { fmt.Printf("Title: %-30s | Address: %p\n", bp.Title, bp) } }
The Makefile:
# Usage: # make run DIR=01-loop-reference-pitfall # make build DIR=01-loop-reference-pitfall # make clean GO=go run: @echo "👉 Running $(DIR)/main.go..." cd $(DIR) && $(GO) run main.go build: @echo "🔧 Building binary from $(DIR)/main.go..." cd $(DIR) && $(GO) build -o ../bin/$(notdir $(DIR)) clean: @echo "🧹 Cleaning up built binaries..." rm -rf bin/
Build and Run The Code
❯ go mod init github.com/geekcoding101/learning-golang ❯ make run DIR=01-loop-reference-pitfall
As you see, the Address didn't change at all!
🐛 The Problem: Reuses the Loop Variable in Golang Range Loop Reference
In Go, when you do:
This means every pointer in the slice is just pointing to the same memory location, which at the end holds the value of the last book.
✅ The Fix: Indexing Directly
The correct way is to use an index:
fmt.Println("\n✅ Fixed Version:") var fixedPointers []*Book for i := range originalBooks { fixedPointers = append(fixedPointers, &originalBooks[i]) } for _, bp := range fixedPointers { fmt.Printf("Title: %-30s | Address: %p\n", bp.Title, bp) }
Now, each pointer actually refers to the corresponding element in the original slice. Problem solved!
💻 Real Code Example on GitHub
I've documented this bug and fix in my GitHub repository:
👉 github.com/geekcoding101/learning-golang
Here's what you’ll find:
-
The buggy version (with all pointers pointing to the same book)
-
The fixed version (each pointer is correct)
-
A
Makefile
to help you run and build each learning topic
✍️ Follow My Golang Tutorials
I’ll continue sharing these hands-on lessons as I deepen my understanding of Go.
Check out my blog 👉 www.geekcoding101.com — where I share practical posts, breakdowns, and real-world insights from my coding journey.
📌 Quick Hashtag
#Golang Range Loop Reference, #for loop golang range, #for loop range golang, #golang for range loop, #for loop in golang with range, #go slice reference, #go for loop pointer trap, #why does my go loop store the same pointer, #golang how to correctly get pointer from loop, #go for loop pointer always same