The Go init hook surprises many newcomers because you can write func init() without calling it anywhere, and the runtime still runs it. Searches such as init golang, golang init, golang init function, go init function, golang func init, go func init, init function golang, func init golang, init function in golang, and golang init() usually boil down to two questions—what init may do, and in what order it runs relative to variables, imports, and main. This article answers both and ends with practical guidance on when init is a good fit.
Tested with Go 1.24 on Linux.
What the golang init function is
In Go, init is not something you invoke by name. You declare one or more functions named init with no parameters and no results:
func init() {
// runs automatically during package initialization
}The compiler treats each func init() as part of package initialization: package-level variables are set up first, then every init in that package, before main runs in a package main program.
Useful rules:
initis optional; many packages never define it.- A package may define multiple
initfunctions, including across several.gofiles. - You cannot call
initfrom your own code, and you cannot refer toinitlike a normal function value.
Order of execution: packages, variables, and init
Understanding golang init order means separating startup into layers: imported packages first (dependency order), then your main package.
Imported packages
When main imports other packages, Go walks the import graph in a valid dependency order. If main imports a and a imports b, then b is initialized before a, and a before your main package runs its own initialization. If main imports both a and b and neither imports the other, the language still picks a valid order; do not rely on unspecified tie-breaking between independent packages for program correctness.
For each package, initialization runs in this order:
- Package-level variable initializers (respecting dependencies between variables).
- Every
initfunction in that package, in the order they appear in the source, file by file, with files ordered lexically by file name.
So golang init function order follows imports and the spec’s initialization algorithm, not “alphabetical package names” as a primary rule.
The main package
After every imported package needed by main has finished variable setup and init work, the main package’s own package-level variables run, then its init functions, and finally main().
Single file: variables before init, init before main
In one file, variable initializers still run before init, and all init functions run before main:
package main
import "fmt"
var sequence = initSequence()
func initSequence() int {
fmt.Println("initSequence()")
return 0
}
func init() {
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func main() {
fmt.Println("main")
}initSequence()
init 1
init 2
mainMultiple files in one package: file name order
If init functions live in different files of the same package, Go processes files in lexical order by name. The example below is meant to be split into three files in one directory; run with go run . from that folder.
// a.go
package main
import "fmt"
func init() {
fmt.Println("init a.go")
}// z.go
package main
import "fmt"
func init() {
fmt.Println("init z.go")
}// main.go
package main
import "fmt"
func main() {
fmt.Println("main")
}Because a.go sorts before z.go, you should see init a.go before init z.go, then main.
Multi-package example (go init across packages)
Here main imports two sibling packages. Each prints from init and exposes a small API. Every imported package finishes variables and init before the importer’s init and main run. Save the layout on disk (for example under a module root) and run go run . from that directory; the Playground-style Run button is not used here because multiple files and local module paths are required.
module example.com/initdemo
go 1.24// a/a.go
package a
import "fmt"
func init() {
fmt.Println("init a")
}
func Greet() {
fmt.Println("greet a")
}// b/b.go
package b
import "fmt"
func init() {
fmt.Println("init b")
}
func Greet() {
fmt.Println("greet b")
}// main.go
package main
import (
"fmt"
"example.com/initdemo/a"
"example.com/initdemo/b"
)
func init() {
fmt.Println("init main")
}
func main() {
fmt.Println("main")
a.Greet()
b.Greet()
}init a
init b
init main
main
greet a
greet bIf you are creating the tree from scratch, run go mod init example.com/initdemo once at the module root, then go run ..
Blank imports and side-effect inits
A line such as import _ "some/driver" still runs initialization for some/driver: its package-level variables and init functions execute because the import is part of the program, even though you never reference the package name. That pattern is common for SQL drivers and image format registration.
When to use or avoid golang init()
init is powerful because it runs automatically, which is also why it is easy to overuse.
Reasonable uses:
- One-time registration that must happen at process start (for example
database/sqldrivers orimageformat decoders) where the library’s API is built aroundRegisterininit. - Small, deterministic package-level setup that cannot fail, or where failing fast with a panic before
mainis an explicit product decision (still a last resort).
Prefer not to use init for:
- Work that can fail (network, files, parsing config): return errors from
main, constructors, or explicit start functions instead. - Global mutable state hidden behind
init, which complicates tests and reuse. - Libraries that impose heavy side effects on every importer.
If you feel you need init order between two packages you control, treat that as a design smell: prefer explicit functions and wiring from main instead of hidden coupling through init.
Summary
The Go init function is any func init() in a package. After imported packages are initialized in dependency order, each package runs its variable initializers, then every init in that package—ordered by file name across files and by source order within a file—before your main package does the same and finally calls main. Use init for narrow registration and similar package-local hooks; keep failure-prone or policy-heavy setup in ordinary functions and main so startup stays obvious to readers and tests.

