Go Program Execution Order

init, main, and Package Initialization

Go executes packages in a deterministic order: first, all imported packages are initialized recursively (depth-first), then each package's `init()` functions run in the order they appear in the source file, and finally, the `main()` function executes.

Go executes packages in a deterministic order: first, all imported packages are initialized recursively (depth-first), then each package's init() functions run in the order they appear in the source file, and finally, the main() function executes. This ensures dependencies are fully ready before any code in the importing package runs.

The initialization sequence follows these strict rules:

  1. Imports: The compiler resolves imports recursively. If Package A imports B and C, B initializes completely before C starts.
  2. Package Init: Within a single package, all init() functions run sequentially in the order they are declared in the file (top to bottom). If multiple files exist in a package, the Go compiler orders them alphabetically by filename.
  3. Main: Only after all imported packages and the main package itself are initialized does main() begin.

Here is a practical example demonstrating the execution flow across multiple files and packages:

// main.go
package main

import (
    "fmt"
    "myapp/utils" // Importing a local package
)

func init() {
    fmt.Println("Main package init()")
}

func main() {
    fmt.Println("Main function start")
    utils.DoSomething()
}
// utils/utils.go
package utils

import "fmt"

func init() {
    fmt.Println("Utils package init()")
}

func DoSomething() {
    fmt.Println("Utils function called")
}

Output:

Utils package init()
Main package init()
Main function start
Utils function called

Notice that utils initializes before main's init() runs. If you have multiple init() functions in main.go, they run top-to-bottom. If you have init_a.go and init_b.go in the same package, init_a.go runs first because of alphabetical sorting.

Key Takeaways for Debugging:

  • Side Effects: Avoid heavy logic in init(); use it only for configuration or registration.
  • Ordering: If Package A needs a variable from Package B, ensure B is imported in A. You cannot rely on init() order between unrelated packages.
  • Cycles: Circular imports (A imports B, B imports A) will cause a compile-time error because the initialization order becomes undefined.

To verify your specific initialization order during development, simply add fmt.Println statements to your init() functions. This is the standard way to trace execution flow without needing external tools.