What does a Go init() function do, and when does it run relative to main()?
answer
- nobody ever calls this function
- it is finished before your entry point starts
- variables first, then these
- you may declare several, even per file
- no parameters, no results, no error
basics
~10 sGo runs a package's init() functions automatically at startup: after that package's package-level variables are assigned and after every package it imports is fully initialized, but before main() begins. You cannot call init() yourself.
solid answer
~40 s`func init()` is a hook the Go runtime calls for you during program initialization. It takes no parameters, returns no results, and cannot be called or referenced from your code. For one package the sequence is: package-level variables are assigned first, then the package's init() functions run. Across the program, a package is initialized only after every package it imports is done, so `main`'s init() functions run last and `main.main` starts only when the whole program is initialized. A package may declare several init() functions, in one file or spread across files. All of this runs sequentially on a single goroutine, so anything you set up in main() -- flag parsing, the logger, config loading -- has not happened yet while init() is running.
code
go · 11 linesvar registry = map[string]int{}
func init() {
registry["cpu"] = 1
}
func init() {
registry["mem"] = 2
}
// Both run at startup, after registry is assigned and before main().go deeper
Be ready to state the fixed signature, that you never call it yourself, and the running order: package-level variables, then init(), then main(). Mentioning that a package can have several init() functions shows you have actually read Go code.
An interviewer at this level expects the mechanics: imports are initialized before importers, each package exactly once, all on a single goroutine, and no way to return an error so a panic is the only failure signal.
Show the production consequence: work done in init() runs before the logger, flags and config that main() sets up, and it runs in every test binary that imports the package. Be able to say what you move out of init() and why.
Own the rule for the codebase: what a package is permitted to do at import time, since that cost falls on every consumer and cannot be opted out of, and what the reviewable alternative -- an explicit constructor -- looks like.
### The one function you never call Go gives every package a startup hook: a function declared as `func init()`. It is unusual among Go functions in three ways. It takes no parameters and returns no results, so the signature is fixed. It cannot be called or referenced from Go code -- you cannot invoke `init()`, take its address, or store it in a variable. And it may be declared more than once: a single package can have many init() functions, several of them even in the same file. The compiler collects them and the runtime invokes them for you. ### The order, precisely Within one package, initialization has two phases: 1. **Package-level variables are assigned.** Not in source order, but in dependency order: a variable is initialized after everything its initializer expression references. 2. **The package's init() functions run**, in the order the source files are presented to the compiler. The `go` command presents a package's files sorted by filename, so in practice it is filename order, then top-to-bottom within each file. Across the program, the rule is: **imported packages are initialized before the package that imports them**, and each package is initialized exactly once no matter how many packages import it. Initialization therefore walks the import graph bottom-up. Package `main` is the last one initialized, and only then does the runtime call `main.main`. ### Everything main does happens afterwards This is the practical consequence juniors get asked about. If `main()` calls `flag.Parse()`, configures a logger, or loads a config file, none of that has happened while init() functions are running. A package that reads a flag's value in init() sees the flag's default. A package that logs in init() logs through whatever the default logger is, not the one main installs. A package that reads the environment in init() reads it before main had a chance to validate or override anything. ### Failure has exactly one exit An init() function has no way to return an error. Its only ways to report failure are to panic or to call `os.Exit`. An unrecovered panic in init() prints the panic message and a goroutine stack trace to standard error and terminates the process with a non-zero exit status -- before `main.main` is ever entered. Nothing in main() can recover it, because `recover` only works inside a function deferred by the panicking goroutine's active call stack, and main is not on that stack yet. Only a deferred `recover` inside that same init() (or something it called) can stop the unwinding. ### Concurrency during initialization Package initialization -- variable assignment and init() calls -- happens in a single goroutine, sequentially, one package at a time. The runtime will not start the next init() until the current one has returned. An init() function is allowed to start goroutines, and those goroutines can run concurrently with the rest of initialization, but anything they wait on that only main() will provide will not appear until initialization has finished. ### What init() is legitimately for Cheap, deterministic setup that cannot fail and that every user of the package needs: precomputing a lookup table, compiling a template or regular expression once, or adding an entry to a registry the package itself owns. In a metrics-reporting daemon, for example, having each collector add itself to a package-level registry during initialization keeps the registration next to the collector that owns it. ### What it is not for Anything that reads ambient state or can fail: dialling a network service, opening a file, reading configuration from the environment, starting background goroutines. All of that runs unconditionally for anyone who imports the package -- including a test binary that only wanted one pure function out of it -- and it cannot be configured, ordered, skipped, or error-checked by the caller. The idiomatic alternative is an exported constructor such as `func New(cfg Config) (*T, error)` that main() calls when it is ready, or, when a genuine package-level singleton is needed, building it lazily on first use. ### The one-line summary to say out loud "Variables first, then init(), imports before importers, main package last, and all of it before main() runs -- on one goroutine, with no way to return an error."
- Can one package declare more than one init() function?Yes -- any number, in one file or spread across the package's files. They run in the order the files reach the compiler, which for the `go` command means filename order and then top-to-bottom within each file. Relying on that order is fragile: renaming a file silently reorders your startup.
- What happens if an init() function panics?The runtime prints the panic message and a stack trace to standard error and exits with a non-zero status, before main() is ever entered. Nothing in main() can recover it, because main is not yet on the call stack. Only a `recover` in a function deferred inside that init() itself can stop it.
- Can init() take arguments or return an error?No. The signature is fixed as `func init()` with no parameters and no results, so there is no channel for configuration in or for failure out. That is precisely why failable setup belongs in an exported constructor that returns `(T, error)` and is called from main().
Think of it as the stagehands' work: by the time the curtain rises on main(), every prop is already placed, and the audience never sees who placed it.
saying these in an interview costs you the question
- Says init() runs after main() has started
- Thinks you can call init() explicitly like a normal function
- Believes a package may declare only one init()
- Claims init() can return an error to its caller
- Says init() runs once per import rather than once per package
- Assumes flags are already parsed inside init()