In a Go program with many imported packages, in what order are those packages initialized?
answer
- leaves of the graph go first
- not the order of the import lines
- five importers, one initialization
- one goroutine, one package at a time
- the entry point's package is last
basics
~20 sBottom-up over the import graph: a package is initialized only after every package it imports is finished, and each package is initialized exactly once however many importers it has. Package main goes last, then main.main runs.
solid answer
~40 sThe runtime repeatedly initializes the next package whose imports are all already initialized, so initialization walks the import graph from the leaves upward. A package imported by five others is still initialized only once. Within each package the order is fixed: package-level variables in dependency order, then that package's init() functions in the order the source files reach the compiler -- filename order under the `go` command. Package `main` is therefore the last package initialized, and `main.main` is called only when the whole program is done. All of it runs sequentially on a single goroutine: the next init() is not invoked until the previous one has returned. An init() may start goroutines, and those run concurrently, but initialization itself is never parallel.
code
text · 4 linespackage metrics -> variables, then init()
package collector -> variables, then init() (imports metrics)
package main -> variables, then init() (imports collector)
main.main()go deeper
Recall the headline: everything a package imports is ready before that package starts, and the package holding main() is finished last. Do not worry about tiebreak rules yet.
Be ready to state the full sequence -- graph bottom-up, once per package, variables then init() inside a package, single goroutine, main last -- and to name what does not affect it, such as the order of import lines.
Show that you treat the implied ordering as a liability: it lives in the import graph, changes when someone adds an import, and cannot be reproduced in a test. Explain what you move into explicit startup code in main.
Own the consequence for shared packages: import-time cost and ordering are paid by every consumer's binary and test suite, so decide as policy how much a widely imported package may do before main() is entered.
### The shape of program startup A Go program's initialization is a bottom-up walk of the import graph. The rule is short: **if a package has imports, those imported packages are initialized before it is**, and a package that several others import is initialized only once. Equivalently, the runtime repeatedly initializes the next package all of whose imports are already done, until nothing is left. Package `main` sits at the top of the graph, so it is always last, and `main.main` is called only after the final init() has returned. For a metrics-reporting daemon whose `main` imports a `collector` package which imports a `metrics` package, the order is: ``` metrics variables, then its init() functions collector variables, then its init() functions main variables, then its init() functions main.main() ``` ### Within one package Each package's own turn has two phases, always in this order: 1. **Package-level variables**, in dependency order -- each assigned after everything its initializer references. 2. **The package's init() functions**, in the order the source files are presented to the compiler. The `go` command sorts a package's files by filename, so it is effectively filename order, then top-to-bottom within a file. That second ordering is real but fragile. Renaming `a_setup.go` to `z_setup.go` reorders your startup with no other change to the code, and no tool will tell you. Treat inter-init ordering inside one package as something you must not depend on: if two pieces of setup have a required order, put them in one init() function, or better, in one explicit setup function. ### It is single-threaded, and that is guaranteed Package initialization -- variable assignment and init() invocation -- happens in a single goroutine, sequentially, one package at a time. The runtime will not invoke the next init() until the previous one has returned. This is why you never need a mutex around package-level state that is only written during initialization, and why a slow init() delays everything behind it, including the process becoming ready. An init() function is allowed to launch goroutines, and those goroutines run concurrently with the remaining initialization. But they cannot rely on anything that only main() will provide -- flags, config, a started server -- because none of that exists yet. A goroutine started in init() that waits on something main() sets up simply sits there until initialization finishes. ### Once, not once per importer A package imported by twenty other packages runs its variable initialization and its init() functions exactly once for the process. This is what makes package-level registries workable: whichever collector package is reached first, the registry package it depends on is already initialized, and it stays initialized for everyone else. It also means initialization cost is per process, not per use. A package that spends 200ms building a table at import time costs every binary that imports it 200ms at startup, including every test binary, and including binaries that never call into it. ### What does not control the order Three things that people assume control it and do not: - **The order of import lines.** The import block is sorted by `gofmt` for readability. The initialization order comes from the graph, not from the text. - **Which package "calls" the other first at run time.** Initialization is complete before any of your code runs; run-time call order is irrelevant. - **Directory layout.** Only the import edges matter, plus filename order as a tiebreak among a single package's files. ### The consequence for design Because the order is determined by imports and cannot be influenced by the consumer, any ordering requirement between packages is encoded implicitly in the import graph -- invisible in review, and silently changed by an added or removed import. That is the strongest structural argument for keeping import-time work trivial and putting real sequencing into `main`, where the order is written down as ordinary statements that a reader can see and a test can reproduce. ### The answer to give "Imports before importers, each package exactly once, variables then init() inside a package, `main` last, and all of it sequentially on one goroutine before `main.main` is called."
- If five packages import the same package, how many times does its init() run?Once. Initialization is per package per process, not per import edge. That is what makes a package-level registry or a precomputed table safe: the first importer to be initialized triggers it, and everyone afterwards sees the finished result.
- Does the order of the import lines in a file affect initialization order?No. Import lines are sorted by gofmt purely for readability. Initialization order is derived from the dependency graph -- imports before importers -- so reordering or regrouping the import block changes nothing at run time.
- Can two packages' init() functions run at the same time?No. Initialization runs sequentially on a single goroutine, one package at a time, and the next init() is not invoked until the previous one returns. An init() may itself start goroutines that run concurrently, but the initialization sequence itself is never parallel.
- If two init() functions in one package must run in a specific order, what do you do?Merge them into one init() function, or move the sequencing into an explicit setup function called from main(). The order between separate init() functions comes from filename order under the `go` command, so a file rename would silently reorder them.
saying these in an interview costs you the question
- Says packages initialize in the order of the import lines
- Thinks an imported package initializes once per importer
- Believes init() functions across packages run concurrently
- Claims package main is initialized before its dependencies
- Assumes directory nesting determines initialization order