When we first start with Go, the main function seems almost too simple. But before main even begins, the Go runtime orchestrates careful initialization of all imported packages, runs their init functions and ensures everything is in the right order.
Program Startup Sequence
sequenceDiagram
participant OS
participant Runtime
participant Packages
participant main
OS->>Runtime: Execute binaryRuntime->>Runtime: Bootstrap (scheduler, GC, memory)Runtime->>Packages: Init packages in dependency orderPackages->>Packages: Run init() functionsPackages-->>Runtime: All packages initializedRuntime->>main: Call main()main->>main: Main goroutine runsmain-->>OS: os.Exit or return</code></pre></figure>
Package Dependency Ordering
Go initializes packages in dependency order, not import order. Leaf packages initialize first.
graph TD
main["main\n(init last)"]
A["Package A"]
B["Package B"]
C["Package C"]
main --> A
main --> B
A --> C
Init order: C -> A -> B -> main.
Multiple init Functions
You can have multiple init functions. They run in order they appear. Go initializes packages in the order of their dependencies.
Use sync.Once when you need lazy or guarded initialization of shared resources. It’s safer than relying on init() ordering.
Exiting with os.Exit()
os.Exit() terminates the program immediately. No deferred functions run.
The main function runs in a special goroutine. If main returns, the entire program exits – even if other goroutines are still working. Use sync.WaitGroup to wait for background goroutines.