ProgrammingSenior Go Developer

What are the peculiarities of working with global variables in Go, how to avoid their incorrect initialization and concurrency during startup (init order)? What are the typical traps?

Pass interviews with Hintsage AI assistant

Answer.

In Go, global variables are initialized in the order of file declarations and the sequence of their init functions. This affects the correctness of accessing the global state: if a variable in one package depends on the value of a variable in another package, but that package has not been initialized yet — it will result in an error or obscure behavior.

There is no way to directly set the "startup order" between init functions of different packages. The compiler determines this order based on the import graph.

  • All global variables are initialized before the call to the main.main() function.
  • It is not recommended to initialize the state with excessive dependencies during package init!
  • For thread safety of global variables, use sync.Once or mutexes during complex initialization.

Example of safe lazy initialization:

var cfg *Config var once sync.Once func GetConfig() *Config { once.Do(func() { cfg = LoadConfigFromDisk() }) return cfg }

Trick question.

Question: Can init functions of different packages run simultaneously (in parallel) when starting a Go program?

Correct answer: No, init functions and global variables are executed strictly sequentially in the order determined by the compiler when analyzing dependencies between packages. There is no parallel execution of init functions.


Examples of real errors due to ignorance of the subtleties of the topic.


Story

In a large project, several modules loaded global configs from disk via init(). One module depended on another, but due to restructuring of the go.mod graph, the initialization order changed — and suddenly the config values were empty! The error manifested randomly, depending on the build order, and was only found after analyzing the dependencies and moving the initialization to an explicit function.


Story

In a REST service, a global map was used for caching directories without mutex. Initialization began from several goroutines that started in init. As a result — data race, periodic panics, invalid data inside the map. After switching to sync.Once and explicitly calling initialization, the problem went away.


Story

A global logger was created in the init function of one of the helper packages, but another package accessed this logger also at startup, before it was initialized. As a result, part of the startup logs was lost while the logger did not yet exist. The solution — dependency injection and explicit initialization of the logger before launching all services.