Declared and Not Used in Go and Why It Is an Error
The Go compiler rejects a local variable that is never read. See the exact message in seven forms, the quick fixes and why assigned but unread counts.

Everyone meets this one in their first hour with Go, and many people keep resenting it. The compiler refuses to build a program that declares a local variable and never reads it. Other languages give you a warning that you learn to ignore. Go gives you a build failure, and the trade is deliberate: a variable that nothing reads is either dead code or a mistake, and a compile error makes sure one of the two gets dealt with. This article shows the message in every shape it takes, with the compiler's real output, then the fixes and the reasons the rule is there. It builds on your first program and its compile errors and the shadowing trap, which is the other thing that produces this message. It sits in the Go basics path.
Tested with Go 1.25.6, macOS on Apple Silicon (arm64)
The exact message, in seven forms
The compiler reports every unused local variable, with the file, line and column of its declaration. Here is one program that has seven different ways to trigger it, and the output below shows all seven at once:
package main
import "fmt"
func main() {
count := 0
for i := 0; i < 3; i++ {
count = i
}
result, err := compute()
fmt.Println(result)
x := 1
x = 2
var unused int
f := func() {
inner := 5
}
f()
if v, ok := lookup(); ok {
fmt.Println("found")
}
switch y := 3; {
case true:
}
}
func compute() (int, error) { return 1, nil }
func lookup() (int, bool) { return 1, true }# example.test/lab
./main.go:6:2: declared and not used: count
./main.go:11:10: declared and not used: err
./main.go:14:2: declared and not used: x
./main.go:17:6: declared and not used: unused
./main.go:19:3: declared and not used: inner
./main.go:22:5: declared and not used: v
./main.go:25:9: declared and not used: yThe message format in Go 1.25 is declared and not used: name. Older compilers and many older tutorials use the wording name declared but not used, so a search should try both. Each line of the output explains one shape of the mistake:
count is assigned inside the loop and never read afterward. People expect this to count as use, and it does not. The compiler's rule is that a variable is used only when its value is read somewhere. Writing to it is not enough, which is why x := 1; x = 2 also fails.
err is the one that matters. The call result, err := compute() returned an error, and the program never looked at it. The compiler caught a missing error check for you. Other languages would have compiled it and swallowed the failure.
unused is a plain var that nothing touches, left behind after a refactor.
inner is declared inside a closure that never reads it. Unused variables are reported inside function literals as well.
v comes from the if v, ok := lookup(); ok form. The ok is used in the condition, and v is not used in the body. The fix is to write _ for the value you do not need.
y is declared in a switch header and never used by any case.
The fixes, in the order I use them
Delete it. If the variable is leftover from an experiment, it is dead code, and the compiler has told you so. Remove the declaration and the assignments to it.
Use it. If it should have been read, the compiler found a logic bug. For the err above the fix is the check you meant to write:
package main
import (
"errors"
"fmt"
)
func compute() (int, error) { return 0, errors.New("compute failed") }
func main() {
result, err := compute()
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(result)
}error: compute failedDiscard it with the blank identifier. When you really do not need a returned value, _ says so explicitly: result, _ := compute(), for i := range s instead of for i, v := range s. The first is a decision about an error and deserves a comment, since a discarded error is the commonest source of silent failures, as handling errors in Go explains. For values other than errors, _ is routine.
Park it with _ = x. While you are writing a function and have not reached the part that uses the variable, a line _ = x satisfies the compiler. It reads as "this is unfinished", and a reviewer will ask about it. Treat it as a placeholder to remove before committing, not as a fix. Some teams run a linter that bans it for exactly that reason.
Why the compiler works this way
The rule has two justifications. First, unused variables are overwhelmingly bugs: a typo that created a new variable instead of assigning to an existing one, a shadowed err (the shadowing article shows how the compiler's complaint is often the only hint), or code after a refactor that no longer does what it did. Second, the Go team wanted builds that always succeed on clean code, so a warning that can be ignored was rejected in favor of a rule that cannot. The cost is the minute you lose when you comment out a line to debug and the compiler objects that a variable lost its only reader. That minute is the entire cost of the rule, and teams mostly agree it is worth paying.
The same philosophy applies to imports. An unused import is an error, "os" imported and not used, and goimports or your editor removes it on save. The two checks together keep a Go file free of dead references in a way no style guide has to ask for.
What is not checked
The rule covers local variables and imports. It does not cover:
- Function parameters. An unused parameter is legal, since the signature may be dictated by an interface. Name it
_or leave it as it is. - Package-level variables and constants. They are legal even if unused, and linters such as
staticcheckreport them (U1000). - Named results. A named return value that you never assign still returns its zero value.
- Struct fields, functions and types. Same as package-level: legal, and found by linters.
- Variables used only through
_ = xor passed to a function. Any read counts, however trivial.
So the compiler's check is narrower than "dead code". Run staticcheck ./... or golangci-lint run to find the rest.
Version notes
The wording of the message has changed across releases, and the behavior has not changed since Go 1.0. If you use an older compiler or an older tutorial, the position of the variable name in the message differs, and the fix is the same. The go vet tool does not report this, since the compiler already does, and gopls shows the same error inline in the editor as you type, so in practice you see it before you build.
When the rule gets in the way
Experimentation is where it hurts. While you are exploring an API in a scratch file, you will declare variables to print later, comment out the print and hit the error. The workable approaches are _ = x, a fmt.Println(x), or a test function where you assert on the value. In code you intend to keep, none of these should survive review. Generated code and code produced by templates sometimes triggers the error because the generator emits variables it does not always use, and the generator's output should include a _ = for those cases. And if a variable is assigned for its side effect in a block where a loop or condition reads it later, check that the later read is in the same scope, because a variable you re-declare in an inner block with := is a new variable that the compiler may flag, as in the shadowing case. The language rule is in the specification's section on declarations and scope, where implementations are told they may make this restriction, and the Go FAQ explains the decision in Why does the compiler reject unused variables.


