In what order does Go initialize package-level variables when one depends on another?
answer
- the compiler reorders, you do not
- who references whom decides
- source position is only a tiebreak
- mutual references do not get zero values
- declaration order spans every file in the package
basics
~20 sIn dependency order, not source order. Go assigns each package-level variable only after everything its initializer references has been assigned, regardless of file or line. Declaration order merely breaks ties, and a mutual dependency is a compile error.
solid answer
~40 sGo does not initialize package-level variables top to bottom. It repeatedly picks the variable that is earliest in declaration order among those whose initializer no longer depends on an uninitialized variable, so `var a = b + 1` declared above `var b = f()` still gets `b` first and `a` ends up 3. Dependencies are traced through functions too: if an initializer calls a function that reads another package-level variable, that variable is a dependency. Declaration order only decides between variables that do not depend on each other, and it spans the whole package -- files are ordered as the `go` command presents them, sorted by filename. A cycle among initializers is not resolved with zero values; it is rejected at compile time as an initialization cycle.
code
go · 6 linesvar a = b + 1
var b = f()
func f() int { return 2 }
// b is assigned 2 first, then a is assigned 3.go deeper
Recall that Go decides the order for you based on which variable needs which, and that a variable declared lower in the file can still be assigned first. Knowing the two-line a/b example is enough at this level.
Be ready to state the stepwise rule precisely: earliest declaration among those with no uninitialized dependency, dependencies traced through function calls, tiebreak spanning every file in the package.
Demonstrate the review instinct: flag package-level variables whose correctness depends on ordering, and note that filename order is a tiebreak a rename can silently change. Prefer values with no ordering requirement at all.
Set the standard for what may live at package level in shared code, since ordering is invisible to consumers and untestable in isolation, and decide when a value must move into explicit setup that the entry point sequences.
### The rule Package-level variable initialization in Go is **dependency-driven, not textual**. The specification describes it as a stepwise process: at each step, pick the variable that appears earliest in declaration order among those whose initializer depends on no uninitialized variable, initialize it, and repeat. Source order is the tiebreaker, never the driver. So this package prints 3: ```go var a = b + 1 var b = f() func f() int { return 2 } ``` `a` is declared first but depends on `b`, so `b` is assigned first (2), then `a` (3). Reading the file top to bottom and expecting `a` to be 1 -- `b` being its zero value at the time -- is the classic wrong answer. ### Dependencies go through functions The analysis is not limited to variables named directly in the initializer expression. If an initializer calls a function, everything that function references transitively counts. In the example above, `f` references nothing, but had it read another package-level variable, that variable would have become a dependency of `a` as well. Methods count too: referring to a value implies referring to the methods it may reach. This is a static, syntactic analysis, so it is conservative -- it does not try to prove which branch will actually execute. ### It spans the whole package, not one file A Go package's files are compiled together, and the declaration order that breaks ties is defined over the files as they are presented to the compiler. The `go` command presents them sorted by filename. So two independent variables in `a_collector.go` and `z_collector.go` are assigned in that filename order -- and renaming a file silently changes it. Code whose correctness depends on that ordering is a defect waiting for a rename. ### Cycles are a compile error If two package-level variables reference each other, directly or through functions, the compiler reports an **initialization cycle** and refuses to build: ```go var x = y + 1 var y = x + 1 // initialization cycle ``` Go does not silently fall back to zero values as some languages do with static initializers. This is a deliberate design choice: a cycle has no defensible order, so the program does not compile. ### The blank identifier still participates `var _ = mustRegister("cpu")` is initialized like any other package-level variable -- the call runs during the variable phase, before any init() function in the package. It is the one way to run code at initialization time in the middle of a declaration block, and it obeys the same dependency rules as a named variable. ### Where variables sit relative to init() All package-level variables of a package are assigned before **any** of that package's init() functions run. That is what makes init() a reasonable place to do work that reads those variables, and what makes it wrong to expect a variable's initializer to see something an init() function set. Across packages, the ordering nests: every imported package is fully initialized -- its variables and its init() functions -- before the importing package assigns its first variable. So a package-level variable whose initializer calls into an imported package is safe: that package is already done. ### Why it matters in real code In a metrics-reporting daemon whose collectors add themselves to a package-level registry during initialization, the registry map itself is a package-level variable. If a collector's registration is also a package-level variable in the same package, the dependency analysis puts the map first -- because the registration references it -- and it works. The moment the registration moves into a different package, the guarantee that saves you is the cross-package one: imports finish before importers. The failure mode to remember: a package-level variable that is assigned but whose value depends on state set later by another package's init() will read as its zero value, and nothing warns you. That is why the safest package-level variables are the ones with no ordering requirement at all -- constants, literal maps, precompiled templates -- and why anything with a real ordering requirement belongs in explicit setup code that main() calls in the order it chooses. ### What to say in an interview State the rule (dependency order, declaration order as tiebreak, package-wide, cycles rejected at compile time), give the two-line `a`/`b` example, and add that all variables precede all init() functions in the same package.
- What does the compiler do when two package-level variables reference each other?It refuses to compile and reports an initialization cycle. Go does not pick an order arbitrarily and does not let one of them observe the other's zero value. The fix is to break the cycle -- usually by computing one of the values in a function that runs later, or by collapsing both into a single declaration.
- Do variables in different files of the same package share one ordering?Yes. A package's files are compiled together and the dependency analysis covers all of them at once. Declaration order, used only as a tiebreak between independent variables, runs across the files in the order the `go` command presents them, which is sorted by filename.
- Is `var _ = something()` initialized, given the blank identifier?Yes -- a variable declared with the blank identifier is initialized like any other, so the call runs during the variable phase, before the package's init() functions. It participates in the same dependency analysis, so anything it references is assigned first.
- Can a package-level variable's initializer see something set by an init() function in the same package?No. Every package-level variable in a package is assigned before any of that package's init() functions run, so the variable would read the zero value. Reverse the dependency: let init() read the variable, not the other way round.
saying these in an interview costs you the question
- Says package-level variables initialize strictly top to bottom
- Thinks a mutual reference resolves to a zero value
- Believes each file is initialized independently
- Claims init() runs before package-level variables are assigned
- Assumes the order of import lines controls variable order