You paste code and the build fails
You copy a snippet from a tutorial that connects to a database. You paste it into your project, add the import line, and run go build. The compiler stops immediately with imported and not used: "github.com/mattn/go-sqlite3". You stare at the code. You clearly need that import for the database to work. The error feels wrong. You are not wrong to be confused. The compiler is right, but the solution requires understanding how Go handles package initialization and side effects.
Why Go enforces usage
Go treats unused imports as a compilation error. Most languages allow you to import a library and never use it. Go refuses. This rule keeps your codebase clean. It forces you to remove dead dependencies. It makes the dependency graph explicit. If a package is imported, something in your code must depend on it. The compiler enforces this contract.
Unused imports indicate a mistake. You might have forgotten to call a function. You might have copied code without understanding the dependency. You might have renamed a function and left the import behind. The error saves you from shipping code with dead weight. It also speeds up compilation by reducing the surface area the compiler must analyze. Go prefers explicit dependencies over implicit magic.
Minimal example
The error appears when the compiler sees an import but finds no reference to any symbol from that package in the file.
package main
import "fmt"
func main() {
// fmt is imported but never referenced.
// The compiler rejects this with: imported and not used: "fmt"
}
The fix is straightforward. Use the package or remove the import. If you intended to print output, add the call.
package main
import "fmt"
func main() {
// Referencing fmt.Println satisfies the import requirement.
fmt.Println("Hello, world!")
}
The compiler scans the file for symbols. It matches fmt.Println to the fmt package. The import is justified. The build proceeds.
Convention aside: gofmt handles formatting, not logic. It will not remove unused imports. Most editors run goimports on save, which adds and removes imports automatically. If you see this error, your editor might not be configured to run goimports, or you are working in a context where the tool cannot run. Trust the compiler. Unused imports are dead code.
The blank identifier
Sometimes you need a package to run its setup code, but you do not need to call any of its exported functions. This happens with database drivers, cryptographic providers, and template function registries. These packages use the init() function to register themselves globally. You import the package solely for this side effect.
Go requires you to acknowledge this explicitly. You use the blank identifier _ to discard the package name. The underscore tells the compiler you intentionally want the side effects without using the package name directly.
package main
import (
"database/sql"
// The blank identifier imports the package for side effects only.
// The init() function runs and registers the driver.
_ "github.com/mattn/go-sqlite3"
)
func main() {
// sql.Open looks up the driver by name.
// The driver was registered by the import above.
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
}
_ = db
}
Without the underscore, the compiler sees the import and no usage of sqlite3, so it errors. The underscore says "I know this package exports symbols, but I only want the initialization logic." The import runs the init() function. The init() function calls sql.Register("sqlite3", &SQLiteDriver{}). Now sql.Open can find the driver.
The registry pattern
Side-effect imports are the key to the registry pattern. This pattern decouples an interface from its implementations. The database/sql package defines an interface for database operations. It does not know about SQLite, PostgreSQL, or MySQL. Drivers implement the interface and register themselves with a name.
The application imports the driver package. The driver's init() function registers the implementation. The application calls sql.Open with the name. sql.Open looks up the name in a global map and returns a connection using the registered driver.
This pattern allows you to swap implementations by changing a single import. You can support multiple databases without conditional logic in your core code. The registry pattern relies on deterministic initialization order. Go guarantees that imports are initialized before the package that imports them. This ensures the driver is registered before sql.Open is called.
// Driver registration happens in the driver package's init function.
// This code lives inside github.com/mattn/go-sqlite3.
func init() {
// Register the driver with the name "sqlite3".
// sql.Open will find this entry in the registry.
sql.Register("sqlite3", &SQLiteDriver{})
}
The registry pattern is idiomatic Go. It avoids inheritance and complex factory functions. It uses the type system and initialization order to wire components together. The blank identifier import is the signal that triggers the wiring.
Initialization order
Understanding initialization order helps you reason about side effects. When you run a Go program, the runtime initializes packages in a specific sequence. The sequence is deterministic and stable.
Imports are processed recursively. If package A imports package B, package B is initialized before package A. Within a package, variables are initialized in the order they appear in the source code. After variables, all init() functions in the package run in the order they appear.
This order guarantees that dependencies are ready before they are used. If your code imports a driver, the driver's init() function runs before your main() function starts. The driver is registered before you attempt to open a connection. You do not need to call a setup function manually. The import handles it.
If you have multiple side-effect imports, they all run before your code executes. The order among sibling imports is deterministic based on the full import path. You can rely on this order, though you should avoid writing code that depends on the order of unrelated packages. Initialization order is a feature, not a hack.
Pitfalls and debugging
The imported and not used error is usually easy to fix, but a few pitfalls can cause confusion.
Using the blank identifier when you should use the package. If a package exports useful functions, do not use _. Use the functions. The blank identifier is only for side effects. If you use _, you cannot reference the package name. The compiler will reject sqlite3.DoSomething if you imported with _. Remove the underscore to use the package name.
Forgetting the blank identifier. You add the import for a driver but forget the _. The compiler errors. Add the underscore. This is the most common fix for this error in real projects.
Circular dependencies. If package A imports package B and package B imports package A, the compiler rejects the program with an import cycle error. This happens before the unused import check. Break the cycle by extracting shared code into a third package.
Large files and commented code. If you have a large file, the error might be hard to track down. You might have imported a package in a helper function but the helper was commented out. Or you renamed a function and forgot to update the call site. Search the file for the package name. If you find no references, remove the import. If you find references inside commented blocks, uncomment the code or remove the import.
Build tags. Sometimes imports are conditional. You might import a package only on Linux or only during tests. Use build tags to control the import. The compiler only sees the import when the tag is active. If the tag is not active, the import is ignored. This prevents unused import errors in builds where the package is not needed.
//go:build linux
package main
import (
// This import is only active when building for Linux.
// On other platforms, the compiler ignores it.
_ "golang.org/x/sys/unix"
)
func main() {
// Linux-specific code here.
}
Convention aside: The receiver name is usually one or two letters matching the type. (b *Buffer) Write(...) is standard. (this *Buffer) is not. Go conventions are consistent. Follow them to make your code readable to other Go developers.
Decision matrix
Use a standard import when you reference exported types, functions, or variables from the package.
Use a blank identifier import when you need the package's init() function to run for side effects like driver registration.
Remove the import when the package is no longer needed in the file.
Use a build tag when the import is only required for specific platforms or build configurations.
Use a helper function to group side-effect imports when you have many drivers and want to keep the main package clean.
Where to go next
- Fix: "cannot use X (untyped string constant) as int value"
- Fix: "cannot take address of" in Go
- Fix: "constant overflows int" in Go
The blank identifier is a signal, not a hack. Trust the compiler. Unused imports are dead code.