What's hidden behind the underscore in import? A guide for those who want to understand Go deeper
Have you ever opened someone else's Go code and seen a line like import _ "github.com/lib/pq"? Your first reaction might be — "Is this even legal? Won't the compiler complain?" Spoiler: it won't. Moreover, this is one of the most elegant features of the language, which often raises questions among beginners. Let's figure out what this mysterious underscore symbol before an import in Go actually does, and how to use it wisely.
On Stack Overflow, this question has gathered thousands of views — and for good reason. The syntax looks like a typo, but behind it lies a deep concept of package initialization and side effects. In this article, we won't just give a dry definition; we'll show real scenarios where you can't do without _. Let's go!
The Problem: "Imported but not used"
Go has a strict rule: every imported package must be used. Otherwise, the code won't compile. This is done for cleanliness and performance — no dead weight. But what if we need to simply execute a package's code when the program starts, without directly calling its functions? This is where the blank identifier — the _ symbol — comes to the rescue.
When you write:
import _ "some/package"— you are telling the compiler: "I know I won't call functions from this package, but please execute its init() function." Go will perform the initialization but won't give you an "unused import" error.
How init() works and why it matters
Every package in Go can contain one or more init() functions. They are executed automatically when the package is imported, before the main() function. The execution order is strictly defined:
- First, the package's variables are initialized
- Then
init()is called - If the package imports other packages — they are initialized first
An example for clarity. Let's create a config package:
// config/config.gopackage config
var AppName string
func init() { AppName = "MyApp v1.0" println("Config initialized:", AppName)}Now in main.go we can import this package "silently":
// main.gopackage main
import ( _ "example.com/project/config" "fmt")
func main() { fmt.Println("Main function started")}Output:
Config initialized: MyApp v1.0Main function startedNotice: we didn't call config.AppName — we simply imported the package with an underscore, and its init() ran automatically. This is the basic mechanism on which many real-world solutions are built.
Main scenario: registering database drivers
The most common example of using import _ in production code is registering database drivers. The standard database/sql package uses a "factory" pattern: all drivers register themselves via sql.Register() in their init() functions.
Take a look at how PostgreSQL is connected (the lib/pq driver):
import ( "database/sql" _ "github.com/lib/pq" // registers the "postgres" driver)
func main() { db, err := sql.Open("postgres", "user=... dbname=...") // ...}Inside lib/pq, the init() function contains something like:
func init() { sql.Register("postgres", &Driver{})}Without the blank import, the driver wouldn't be registered, and sql.Open("postgres", ...) would return an "unknown driver" error. The same works for MySQL, SQLite, Oracle, and other databases.