}

Fix 'declared and not used' Error in Go (2026)

declared and not used

The immediate fix: either use the variable somewhere in its scope, or replace it with the blank identifier _ to explicitly discard it.

Before (broken):

func main() {
    name := "Alice" // declared and not used
    fmt.Println("hello")
}

After (fixed — use it):

func main() {
    name := "Alice"
    fmt.Println("hello,", name)
}

After (fixed — discard it):

func main() {
    _ = expensiveOperation() // explicitly discard the return value
    fmt.Println("hello")
}

Why Go Enforces This

Go's "declared and not used" rule is a compile error, not a warning. This is an intentional design decision by the Go team:

  1. Unused variables are almost always bugs. If you declared a variable and never used it, you either forgot to use it (bug), or you can delete the declaration (dead code). Either way, action is needed.

  2. It keeps code clean. Languages that allow unused variables accumulate dead declarations over time. Go prevents this at the source.

  3. It speeds up compilation. The compiler does not have to track, allocate, or reason about variables that have no effect.

The rule applies to variables declared inside functions with := or var. It does NOT apply to: - Package-level variables - Function parameters - Named return values - Variables declared with var but used as a type assertion target


Case 1: Variable Declared but Never Read

The most straightforward case — you assign to a variable but never read its value.

Broken:

func processUsers(users []User) {
    count := len(users)  // declared and not used
    for _, u := range users {
        save(u)
    }
}

Fixed — use the variable:

func processUsers(users []User) {
    count := len(users)
    log.Printf("processing %d users", count)
    for _, u := range users {
        save(u)
    }
}

Fixed — remove it if not needed:

func processUsers(users []User) {
    for _, u := range users {
        save(u)
    }
}

Case 2: Discarding Error Returns

A very common pattern: you call a function that returns (value, error) and you only want the value. You must handle both return values.

Broken:

func readConfig() Config {
    data, err := os.ReadFile("config.json") // err declared and not used
    var cfg Config
    json.Unmarshal(data, &cfg)
    return cfg
}

Fixed — handle the error:

func readConfig() (Config, error) {
    data, err := os.ReadFile("config.json")
    if err != nil {
        return Config{}, fmt.Errorf("reading config: %w", err)
    }
    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return Config{}, fmt.Errorf("parsing config: %w", err)
    }
    return cfg, nil
}

Fixed — explicitly ignore the error with _ (only when you are certain it cannot fail):

func readConfig() Config {
    data, _ := os.ReadFile("config.json") // _ means: I know about this error, I'm discarding it
    var cfg Config
    _ = json.Unmarshal(data, &cfg)
    return cfg
}

Using _ for errors is not recommended in production code, but it is the correct way to suppress the "declared and not used" compiler error when you consciously choose to ignore an error.


Case 3: Loop Variables

Range loops declare an index and a value variable. If you only need one, you must discard the other with _.

Broken:

func printNames(users []User) {
    for i, u := range users { // i declared and not used
        fmt.Println(u.Name)
    }
}

Fixed — use _ for the index:

func printNames(users []User) {
    for _, u := range users {
        fmt.Println(u.Name)
    }
}

Fixed — use _ for the value (when you only need the index):

func printIndices(users []User) {
    for i := range users { // Go 1.4+: omitting value variable is valid
        fmt.Println(i)
    }
}

Map iteration:

m := map[string]int{"a": 1, "b": 2}

// Only keys:
for k := range m {
    fmt.Println(k)
}

// Only values:
for _, v := range m {
    fmt.Println(v)
}

Case 4: The Blank Identifier _

The blank identifier _ is Go's way of explicitly discarding a value. You can use it anywhere a variable name is expected. It has no storage — nothing is allocated, and the value is genuinely discarded.

// Discard one of multiple return values
result, _ := strconv.Atoi("42")

// Discard all return values
_ = someFunction()

// Discard loop index
for _, item := range items { ... }

// Force interface compliance check at compile time
var _ io.Writer = (*MyWriter)(nil)

You can use _ multiple times in the same assignment, which is unique — you cannot reuse any other variable name in the same short variable declaration.

a, _, c, _ := multiReturn() // valid: _ can appear multiple times

Case 5: Variable Declared in One Branch, Not Used in Another

If you declare a variable conditionally and it is only used in some code paths, the compiler still considers it unused if there is any path where it is declared but not read.

Broken:

func process(debug bool) {
    var logMsg string // declared and not used (in the non-debug path)
    if debug {
        logMsg = "debug mode"
        fmt.Println(logMsg)
    }
    // logMsg is declared but not used when debug == false
    doWork()
}

Fixed — move the declaration inside the branch:

func process(debug bool) {
    if debug {
        logMsg := "debug mode"
        fmt.Println(logMsg)
    }
    doWork()
}

Case 6: Reassignment Without Reading

Using := to reassign a variable without reading it first causes the error because the first assignment is never consumed.

Broken:

func getTotal() int {
    result := 0        // declared
    result := compute() // declared and not used — first result never read
    return result
}

Fixed — use = for reassignment:

func getTotal() int {
    result := 0        // declare with :=
    result = compute() // reassign with =
    return result
}

IDE Tips

Modern Go editors handle this error automatically:

  • VS Code (Go extension): Highlights unused variables inline with a red underline. The Problems panel shows all unused variables in the project.
  • GoLand: Highlights unused variables in grey/orange and offers a quick-fix to delete or replace with _.
  • Vim/Neovim (gopls): The gopls language server reports unused variables in real time.
  • go vet: Does not catch unused variables (the compiler does), but catches related issues like shadowed variables.

staticcheck (separate tool) goes further and catches variables that are written but whose written value is never read — a subtler form of "declared and not used":

go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...

Related Guides


FAQ

Does the rule apply to function parameters?

No. Function parameters can be declared without being used. If you want to document that a parameter is intentionally unused (common when implementing an interface), you can either omit the name entirely or use _:

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // r is not used, but this is fine — it's a parameter, not a local variable
    fmt.Fprintln(w, "hello")
}

Does the rule apply to package-level variables?

No. Package-level var declarations are not subject to the "declared and not used" rule. Only variables declared inside function bodies must be used.

Can I have unused variables during development without commenting code out?

Yes — use _ = myVar as a temporary suppressor while developing. Some developers also use a helper function:

var use = func(vals ...interface{}) {}
// then: use(myVar) — compiles, does nothing, keeps the variable "used"

Remove these placeholders before committing. Your CI linter (staticcheck, golangci-lint) should catch any that slip through.