Question
I want a precise explanation of what the init() function does in Go and when it runs.
I read the explanation in Effective Go, but I am not completely sure I understood this sentence:
And finally means finally:
initis called after all the variable declarations in the package have evaluated their initializers, and those are evaluated only after all the imported packages have been initialized.
What does all the variable declarations in the package have evaluated their initializers mean?
For example, if I declare package-level ("global") variables across files in a package, does init() wait until all of those values are evaluated first, then run all init() functions, and only after that run main() when the program starts?
I also read this explanation:
If a package has one or more
init()functions they are automatically executed before the main package'smain()function is called.
My current understanding is that init() is only relevant when you intend to run main(). Is that correct, or is init() broader than that?
For example:
package main
import "fmt"
var message = createMessage()
func createMessage() string {
fmt.Println("variable initializer runs")
return "Hello"
}
func init() {
fmt.Println("init runs")
}
func main() {
fmt.Println("main runs:", message)
}
In what order do these parts run, and what exactly does Go guarantee?
Short Answer
By the end of this page, you will understand when init() runs in Go, how package-level variables are initialized, how imported packages affect initialization order, and how all of this happens before main() starts. You will also see when init() matters outside of the main() package.
Concept
What init() is in Go
In Go, init() is a special function that is run automatically during package initialization.
You do not call it yourself.
func init() {
// setup work
}
Go uses package initialization in a fixed sequence:
- Initialize imported packages first
- Evaluate package-level variable initializers
- Run
init()functions in that package - If this is the
mainpackage, runmain()
What “variable declarations have evaluated their initializers” means
A package-level variable can be assigned a value when it is declared:
var port = readDefaultPort()
var appName = "My App"
These assigned expressions are called initializers.
readDefaultPort()is an initializer expression"My App"is also an initializer expression
Mental Model
Think of Go program startup like opening a set of nested toolboxes.
- First, Go opens the toolboxes your package depends on
- Each toolbox sets up its own labels and tools first (package variables)
- Then it performs any automatic setup steps inside (
init()) - Only when all required toolboxes are ready does Go open and run the main toolbox (
main())
Another way to picture it:
- package variables = ingredients being prepared
init()= kitchen setup after ingredients are readymain()= start cooking the actual meal
So init() does not prepare the raw ingredients themselves. It runs after those ingredients already exist.
Syntax and Examples
Basic syntax
func init() {
// runs automatically during package initialization
}
You can also have package-level variables:
var name = loadName()
Example: variable initializer, then init(), then main()
package main
import "fmt"
var message = createMessage()
func createMessage() string {
fmt.Println("variable initializer runs")
return "Hello"
}
func init() {
fmt.Println("init runs")
}
func main() {
fmt.Println("main runs:", message)
}
Output:
variable initializer runs
init runs
main runs: Hello
Step by Step Execution
Traceable example
package main
import "fmt"
var a = first()
var b = second()
func first() string {
fmt.Println("first initializer")
return "A"
}
func second() string {
fmt.Println("second initializer, sees a =", a)
return "B"
}
func init() {
fmt.Println("init, sees a =", a, "and b =", b)
}
func main() {
fmt.Println("main, sees a =", a, "and b =", b)
}
Output:
first initializer
second initializer, sees a = A
init, sees a = A and b = B
main, sees a = A and b = B
Step by step
- Go starts initializing the
mainpackage. - It evaluates package-level variables.
- runs first.
Real World Use Cases
Where init() is used in practice
Registering database drivers
Some packages register themselves during initialization.
import _ "github.com/lib/pq"
A blank import is often used specifically so that the imported package's init() runs and registers the driver.
Setting up package registries
A package might automatically register handlers, parsers, encoders, or plugins.
Validating configuration defaults
A package can compute default values before the rest of the program uses them.
Preparing lookup tables
If a package needs a precomputed map or table, it may build it during variable initialization or in init().
When not to use it
Avoid putting major application logic in init().
Examples of poor uses:
- opening database connections automatically
- starting servers
- performing network requests
- doing work that can fail in complex ways
Those actions are usually better handled explicitly from main() or a setup function.
Real Codebase Usage
Common patterns in real projects
1. Registration pattern
Packages often use init() to register themselves with a shared system.
func init() {
registerFormat("json")
}
This is common in:
- database drivers
- image decoders
- plugin-like systems
- metrics exporters
2. Guarded setup of package state
A package may build internal state once during startup.
var supported = map[string]bool{
"json": true,
"xml": true,
}
If the state can be created directly, package variables are often simpler than init().
3. Validation during startup
func init() {
if defaultPort < 0 {
panic("invalid defaultPort")
}
}
Common Mistakes
1. Thinking init() is called manually
You never call init() yourself.
Broken idea:
func main() {
init()
}
This is not how Go is meant to use init().
2. Thinking init() is only for the main package
Wrong assumption:
init()runs only in the package containingmain()
Correct idea:
init()can run in any imported package
3. Doing too much work in init()
Broken style:
func init() {
// expensive, hidden startup behavior
fetchRemoteConfig()
startBackgroundWorker()
}
Comparisons
init() vs other startup approaches
| Approach | Runs automatically | Best for | Notes |
|---|---|---|---|
| Package variable initializer | Yes | Simple initial values | Often clearer than init() |
init() | Yes | Small package setup, registration | Hidden behavior, use carefully |
| Explicit setup function | No | Important startup logic | Easier to test and control |
main() | No automatic call except program entry | Application orchestration | Best place for major startup steps |
Package variables vs init()
Cheat Sheet
Quick reference
Order of execution
1. Imported packages initialize first
2. Package-level variables are initialized
3. init() functions run
4. main() runs (only in package main)
What counts as an initializer?
var x = 10
var y = makeValue()
var name = "Go"
Each right-hand side is an initializer expression.
Rules for init()
- special function name:
init - no parameters
- no return values
- called automatically
- can exist in multiple files in the same package
- runs after package variables are initialized
Good uses
- registering drivers or handlers
- small setup for package internals
- validating fixed internal assumptions
Avoid using it for
- hidden network calls
- database connections with important runtime behavior
- long-running work
- major app orchestration
Basic example
var value = build()
func {
fmt.Println()
}
FAQ
Does init() run before main() in Go?
Yes. In the main package, all package initialization completes before main() runs.
Does init() run before package-level variables?
No. Package-level variable initializers run first, then init() runs.
Can a package have more than one init() function?
Yes. A package may contain multiple init() functions, even across different files.
Does init() run in imported packages?
Yes. Imported packages are initialized before the package that imports them.
Can I call init() myself?
No. It is a special function managed by the Go runtime and compiler rules.
Is init() only useful in executable programs?
No. It is useful in any package that needs automatic initialization, especially registration-style packages.
Should I use init() for configuration and connections?
Usually only for small, predictable setup. For major configuration loading or opening connections, explicit startup code is often better.
Mini Project
Description
Build a small Go program with two packages to observe initialization order directly. This project demonstrates that imported packages initialize first, package-level variables are evaluated before init(), and main() runs last.
Goal
Create a runnable program that prints the exact order of package variable initialization, init() execution, and main() execution.
Requirements
[ "Create one helper package and one main package.", "Add at least one package-level variable initializer in each package.", "Add an init() function in each package.", "Print messages so the execution order is visible.", "Run the program and confirm that main() executes last." ]
Keep learning
Related questions
Check if a Value Exists in a Slice in Go
Learn how to check whether a value exists in a slice in Go, and why Go has no Python-style `in` operator for arrays or slices.
Concatenating Slices in Go with append
Learn how to concatenate two slices in Go using append and the ... operator, with examples, pitfalls, and practical usage.
Convert String to Integer in Go: Idiomatic Parsing with strconv.Atoi
Learn the idiomatic way to convert a string to an int in Go using strconv.Atoi, with examples, errors, and common mistakes.