Skip to main content

Variable Shadowing: The := Trap

One of the most common pitfalls in Go is variable shadowing with the short declaration operator :=.
The := operator creates a new variable in the current scope. If a variable with the same name exists in an outer scope, you’ve just shadowed it!

The Problem

x := 10
if true {
    x := 20 // New variable 'x' (Shadowing). Outer 'x' is still 10.
    fmt.Println(x) // Prints: 20
}
fmt.Println(x) // Prints: 10 (outer x unchanged!)

The Solution

If you want to modify the existing variable, use the assignment operator = instead:
x := 10
if true {
    x = 20 // Assigns to outer 'x'
    fmt.Println(x) // Prints: 20
}
fmt.Println(x) // Prints: 20
Always check your scopes! When in doubt, use explicit = assignment instead of := inside nested blocks.

Goroutine Pitfalls

The Mystery of Empty Output

Why did my code print nothing?When main() finishes, the program dies instantly. It does NOT wait for background goroutines to finish.

The Problem

func main() {
    go func() {
        fmt.Println("Hello from goroutine")
    }()
    // main() exits immediately, goroutine gets killed!
}

The Solution

Use WaitGroups or Channels to make main wait:
func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    
    go func() {
        defer wg.Done()
        fmt.Println("Hello from goroutine")
    }()
    
    wg.Wait() // Wait for goroutine to finish
}

Channel Deadlocks

The Deadlock Trap

Fatal Error: All goroutines are asleep!Writing to an unbuffered channel blocks forever until someone reads it. If you write in main without a background reader, main blocks forever → Deadlock.

The Problem

func main() {
    messageChain := make(chan string)
    messageChain <- "Hello" // BLOCKS FOREVER - no reader!
    msg := <-messageChain   // Never reached
    fmt.Println(msg)
}

The Solution

Either use a buffered channel or send/receive in separate goroutines: Option 1: Buffered Channel
func main() {
    messageChain := make(chan string, 1) // Buffer size 1
    messageChain <- "Hello" // Doesn't block
    msg := <-messageChain
    fmt.Println(msg)
}
Option 2: Separate Goroutine
func main() {
    messageChain := make(chan string)
    
    go func() {
        messageChain <- "Hello" // Send in background
    }()
    
    msg := <-messageChain // Receive in main
    fmt.Println(msg)
}
Unbuffered channels provide synchronization - the sender blocks until a receiver is ready. Buffered channels allow asynchronous communication up to the buffer size.

Data Persistence Myth

Why Did My Deleted Item Come Back?

The Persistence Myth: RAM is volatile. Every go run starts a new process with fresh memory. Data doesn’t persist unless saved to a DB/File.
Many beginners are confused when they modify data (like a map) in their program, stop it, and find the changes gone when they restart.

Understanding Program Memory

  • Each go run creates a new process with fresh memory
  • In-memory data structures (maps, slices, variables) only exist while the program runs
  • When the program exits, all memory is released

The Solution

If you need data to persist:
// Save to file
func saveData(data map[string]string) error {
    file, err := os.Create("data.json")
    if err != nil {
        return err
    }
    defer file.Close()
    
    encoder := json.NewEncoder(file)
    return encoder.Encode(data)
}

// Load from file
func loadData() (map[string]string, error) {
    file, err := os.Open("data.json")
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    var data map[string]string
    decoder := json.NewDecoder(file)
    err = decoder.Decode(&data)
    return data, err
}

Loop Variable Gotcha

Capturing Loop Variables in Goroutines

When launching goroutines inside a loop, be careful about capturing the loop variable!

The Problem

for i := 0; i < 5; i++ {
    go func() {
        fmt.Println(i) // All goroutines may print 5!
    }()
}

The Solution

Pass the loop variable as an argument:
for i := 0; i < 5; i++ {
    go func(n int) {
        fmt.Println(n) // Each goroutine gets its own copy
    }(i)
}
Always pass loop variables as arguments to goroutines to ensure each goroutine gets the correct value.

Summary

Most Go pitfalls stem from:
  1. Scope confusion - Variable shadowing with :=
  2. Concurrency misunderstanding - Goroutines and channels behavior
  3. Memory model confusion - Volatility of RAM
  4. Closure capturing - Loop variables in goroutines
Understanding these concepts deeply will help you avoid 90% of common Go mistakes.

Build docs developers (and LLMs) love