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
Before Go runs init() for a package, it makes sure these package-level variables have already been given their initial values.
So yes: if you have package-level variables across one or more files in the same package, Go initializes them first, then runs that package's init() function or functions.
Important idea: init() belongs to packages, not just main
init() is not only for code that directly calls main().
It runs whenever a package is initialized.
That means:
- if your program imports a package, that package's
init()can run - if package
Aimports packageB, thenB'sinit()runs beforeAis initialized main()is just the final step for executable programs
So init() is broader than main(): it is part of the lifecycle of packages.
Why this matters
This initialization model lets Go ensure that:
- dependencies are ready before the package uses them
- package-level configuration can be prepared early
- registration logic can happen automatically
But because init() runs implicitly, it should be used carefully. Hidden setup can make code harder to understand and test.
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
What this shows
messageis initialized first- then
init()runs - then
main()runs
Example with imported packages
helper/helper.go
package helper
import "fmt"
var Status = buildStatus()
func buildStatus() string {
fmt.Println("helper variable initializer")
return "ready"
}
func init() {
fmt.Println("helper init")
}
main.go
package main
import (
"fmt"
"myapp/helper"
)
var message = makeMessage()
func makeMessage() string {
fmt.Println("main variable initializer")
return "app started"
}
func init() {
fmt.Println("main init")
}
func main() {
fmt.Println(helper.Status)
fmt.Println(message)
}
Expected order:
helper variable initializer
helper init
main variable initializer
main init
ready
app started
The imported package is initialized before the importing package.
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.
a = first()runs first.first()printsfirst initializerand returns"A".b = second()runs next.second()can already reada, becauseawas initialized first.- After all package variables are initialized, Go calls
init(). init()can use bothaandb.- After package initialization is complete, Go calls
main().
Key guarantee
Inside a package, init() runs only after package-level initialization for that package is complete.
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")
}
}
This is sometimes used for impossible or programmer-error conditions.
4. Prefer explicit setup when behavior matters
In large codebases, developers often avoid heavy init() usage because it hides behavior.
Instead of this:
func init() {
connectToDatabase()
}
they prefer this:
func main() {
db := connectToDatabase()
_ = db
}
This makes startup easier to read, test, and control.
Rule of thumb
Use init() for small, automatic package setup.
Use explicit functions for important application behavior.
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()
}
Why this is a problem:
- hard to test
- hard to control
- surprising side effects
4. Assuming file order controls everything
A package can span multiple files. Beginners sometimes assume Go simply runs files top to bottom in filename order.
Do not rely on filename order as your mental model. Think in terms of dependency-based package initialization and package-level declarations.
5. Using init() when a normal variable initializer is enough
Instead of:
var appName string
func init() {
appName = "My App"
}
prefer:
var appName = "My App"
This is simpler and clearer.
6. Expecting init() to return a value or error
init() cannot take arguments and cannot return values.
Invalid:
func init() error {
return nil
}
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()
| Feature | Package variable initializer | init() |
|---|---|---|
| Good for simple values | Yes | Sometimes unnecessary |
| Can contain multiple statements | No | Yes |
| Runs automatically | Yes | Yes |
| Clearer for readers | Usually yes | Sometimes less clear |
init() vs main()
| Feature | init() | main() |
|---|---|---|
| Called automatically | Yes | Yes, but only in package main |
| Exists in non-main packages | Yes | No |
| Used for package setup | Yes | Not usually |
| Used for app control flow | No | Yes |
Best guideline
- Use package variable initializers for simple values
- Use
init()for small automatic setup - Use
main()or explicit functions for major startup work
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 init() {
fmt.Println("package ready")
}
Main idea
init() is part of package initialization, not just main() startup.
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.
What if I have variables in multiple files of one package?
They are still part of the same package initialization process. Go initializes the package-level state before running that package's init() functions.
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
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.