DEV Community

Rijul Rajesh
Rijul Rajesh

Posted on

The Hidden Bug in Go: Variable Shadowing Explained

If you’ve been writing Go for a while, you might have run into a strange situation where a variable doesn’t seem to hold the value you expect. You check your code, and the logic looks fine, but something feels off.

This is often caused by variable shadowing, a subtle issue that doesn’t cause a compiler error but can definitely cause confusion.

What is Variable Shadowing?

Variable shadowing happens when you declare a new variable with the same name as an existing variable in an inner scope. The new variable takes precedence in that smaller scope and hides the outer one. So, any reference inside the inner scope uses the new variable instead of the outer one.

Think about it like this: if you name your child after yourself, inside your house the name refers to the child even though you both share the same name.

A Simple Example

package main

import "fmt"

func main() {
    x := 10
    fmt.Println("Outer x:", x) // 10

    if true {
        x := 20 // This creates a new x that shadows the outer one
        fmt.Println("Inner x:", x) // 20
    }

    fmt.Println("Outer x after if:", x) // Still 10
}
Enter fullscreen mode Exit fullscreen mode

Here’s what is going on:

  • The first x with the value 10 exists in the main function scope.
  • Inside the if block, using := creates a new variable called x that lives only inside that block.
  • Printing x inside the block prints 20, but outside the block, the original x remains unchanged at 10.

Why Variable Shadowing Can Be Problematic

Variable shadowing itself is not inherently bad. The trouble comes when it happens unintentionally, especially in error handling, loops, or when returning values from functions.

Take a look at this example:

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Open("data.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer f.Close()

    data := make([]byte, 100)
    if _, err := f.Read(data); err != nil { // err here is a new variable, shadowing the outer err
        fmt.Println("Error reading file:", err)
        return
    }

    fmt.Println("Data read successfully")
}
Enter fullscreen mode Exit fullscreen mode

At first glance, it seems fine. But notice that inside the if statement, err := declares a new err variable limited to that block. This shadows the err from os.Open. While in this case it might not cause problems, in more complex code, it can lead to bugs where you expect to be checking or using the same variable but actually aren’t.

Why Does Go Allow This?

In Go, the short variable declaration syntax := always declares at least one new variable. When you use it in an inner scope and a variable with the same name already exists in an outer scope, Go creates a new variable instead of reusing the outer one.

This is intentional and consistent behavior. It’s designed to keep variable declarations simple and clear, but it also means we need to be mindful when using := inside nested scopes.

How to Avoid Problems with Shadowing

  1. When you want to assign a value to an existing variable, use the assignment operator = instead of :=.

  2. Use linting tools such as go vet, staticcheck or vscode extensions. They can warn you about variables that might be unintentionally shadowed.

  3. Give your variables clear, descriptive names. Avoid reusing generic names like x, n, or err in different scopes.

  4. Keep variable scopes as small and tight as possible. This reduces the chance of accidentally reusing variable names.

Final Thoughts

Variable shadowing in Go can feel like an invisible trap. It won’t cause your program to fail outright, but it can change behavior in unexpected ways.

Being aware of how it works will help you write clearer code and avoid tricky bugs. Next time you’re puzzled about why a variable isn’t behaving as expected, consider whether shadowing might be involved.

If you're a software developer who enjoys exploring different technologies and techniques like this one, check out LiveReview.

LiveReview delivers high-quality feedback on your PRs/MRs within minutes.
It saves hours per review by providing fast, automated first-pass insights. This helps both junior and senior engineers move faster.

If you're tired of waiting on peer reviews or unsure about the quality of feedback you'll receive, LiveReview is here to help.

Top comments (0)