Question

When Does init() Run in Go? Package Initialization Explained

goinit

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: init is 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's main() 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:

  1. Initialize imported packages first
  2. Evaluate package-level variable initializers
  3. Run init() functions in that package
  4. If this is the main package, run main()

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 A imports package B, then B's init() runs before A is 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 ready
  • main() = start cooking the actual meal

So init() does not prepare the raw ingredients themselves. It runs after those ingredients already exist.

Take Quiz

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

  • message is 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

  1. Go starts initializing the main package.
  2. It evaluates package-level variables.
  3. a = first() runs first.
  4. first() prints first initializer and returns "A".
  5. b = second() runs next.
  6. second() can already read a, because a was initialized first.
  7. After all package variables are initialized, Go calls init().
  8. init() can use both a and b.
  9. 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.

Take Quiz

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 containing main()

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

ApproachRuns automaticallyBest forNotes
Package variable initializerYesSimple initial valuesOften clearer than init()
init()YesSmall package setup, registrationHidden behavior, use carefully
Explicit setup functionNoImportant startup logicEasier to test and control
main()No automatic call except program entryApplication orchestrationBest place for major startup steps

Package variables vs init()

FeaturePackage variable initializerinit()
Good for simple valuesYesSometimes unnecessary
Can contain multiple statementsNoYes
Runs automaticallyYesYes
Clearer for readersUsually yesSometimes less clear

init() vs main()

Featureinit()main()
Called automaticallyYesYes, but only in package main
Exists in non-main packagesYesNo
Used for package setupYesNot usually
Used for app control flowNoYes

Best guideline

  • Use package variable initializers for simple values
  • Use init() for small automatic setup
  • Use main() or explicit functions for major startup work
Take Quiz

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.

Take Quiz

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." ]

Take Quiz

Keep learning

Related questions

View more