notes

What Happens Before main() in Go

2 min read#go

What the Go runtime does before main(): package init order, init functions, and startup orchestration.

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-->>OS: os.Exit or return

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.