— notes

What Happens Before main() in Go

2 min read#go
On this page
  1. Program Startup Sequence
  2. Package Dependency Ordering
  3. Multiple init Functions
  4. Exiting with os.Exit()
  5. The main Goroutine

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 binary
Runtime->>Runtime: Bootstrap (scheduler, GC, memory)
Runtime->>Packages: Init packages in dependency order
Packages->>Packages: Run init() functions
Packages-->>Runtime: All packages initialized
Runtime->>main: Call main()
main->>main: Main goroutine runs
main--&gt;&gt;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.

func main() {
    if err := doSomethingRisky(); err != nil {
        cleanup()
        os.Exit(1)
    }
}

The main Goroutine

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.