| Tested on | RHEL 10.2 with Go 1.26.5 |
|---|---|
| Package | go 1.26.5 |
| Applies to | Any host with Go installed |
| Privilege | Normal user |
| Scope | Fix Go compiler errors for unused local variables and unused imports: delete or use bindings, blank identifiers for intentional discards, blank imports for init side effects, range-loop indexes, and := shadowing in branches. Does not cover full variable-scope tutorials or linter configuration. |
| Related guides | Variable scope in Go Golang if else Return multiple values in Go Getting started with Go Golang iterate over array |
Go rejects unused local variables and unused imports at compile time. Normally you remove the dead declaration or actually use the value. Use _ only when you intentionally need to discard a result or import a package for its init side effects.
Typical compiler messages:
x declared and not used
"fmt" imported and not usedWhy Go reports "declared and not used"
The Go compiler rejects unused variables declared inside function bodies (including short declarations with :=). If you declare count := 0 and never read count, go build stops with count declared and not used. Package-level variables and function parameters follow different rules—unused parameters in a signature do not trigger this message.
This is a compiler error, not a go vet warning. There is no -gcflags or build tag that disables it—the fix is to change the code.
Fix an unused variable
Work through these in order:
- Delete the variable when it serves no purpose.
- Use the value in your logic or output.
- Discard an unwanted result with
_when you must accept a return value but only care about another one.
A common discard pattern is a range loop where you need the element, not the index:
for _, value := range values {
fmt.Println(value)
}For multiple return values, keep the results you need:
line, err := reader.ReadString('\n')
if err != nil {
return err
}
fmt.Println(line)Do not drop err just to satisfy the compiler. _ = variable can silence a binding temporarily while refactoring, but deleting dead code is usually the better permanent fix.
Fix "imported and not used"
If fmt is imported but no identifier from fmt appears in the file, you see:
"fmt" imported and not usedRemove the import line when you added it by mistake or finished refactoring.
A blank import imports the package solely for its initialization side effects; its package name is not available for normal references. Database drivers are a common case:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)The _ means "import for side effects only." It is not a generic way to hide imports you forgot to use—if you do not need the package's init behavior, delete the import instead.
goimports removes unreferenced imports and adds missing ones. Editors using gopls can usually be configured to organize imports automatically on save or format. Install goimports with go install golang.org/x/tools/cmd/goimports@latest if your editor does not already run it. goimports can fix the import list automatically, but it will not decide how an unused local variable should be handled; delete or use that variable yourself.
Ignore unused indexes and return values
Range loops: for i, value := range values declares i even when you only use value. Either use i or replace it with _:
for _, value := range values {
process(value)
}Multiple returns: when you need only one result from a function that returns several non-error values, bind the rest to _ on the left:
key, _, ok := strings.Cut(line, "=")
if !ok {
return fmt.Errorf("missing separator")
}Prefer handling err explicitly in value, err := patterns. Use _ for a return you genuinely do not need, not to ignore errors.
Fix := shadowing and redeclaration mistakes
:= inside an inner block can declare a new variable with the same name as an outer one. If that inner variable is never read, you get declared and not used. When the inner variable is used, the code compiles—but the outer binding can still keep an unexpected value because the inner name shadows it. The Go spec allows an identifier declared in an inner block to shadow one from an outer block.
In the same block, := does not always create every name from scratch: it may redeclare existing variables when at least one non-blank variable on the left is new. For example, value, err := getValue() can reuse an existing value if err is new in that block.
Assign to the existing variable with = when you mean to update it:
var value string
if condition {
value = "yes"
} else {
value = "no"
}
fmt.Println(value)Accidentally using short declaration in the branch creates an inner value that shadows the outer one:
var value string
if condition {
value := "yes"
fmt.Println("inside:", value)
}
fmt.Println("outside:", value)This compiles, but outside: prints an empty string because the outer value was never assigned. For more on block scope, see variable scope in Go.
Summary
declared and not used means a local binding is never read; imported and not used means a package import has no references. Delete dead code first, use _ to discard a result you intentionally ignore (such as a range index), and use _ "path" only for documented init side effects. Inside branches, prefer = on an existing name over := when you are not introducing a new variable. There is no compiler switch to disable these checks.
References
- Effective Go — blank identifier
- Go language specification — declarations and scope
- Go FAQ — unused variables and imports
- goimports command documentation

