Golang global variables: package scope, exports, structs, and pitfalls

Golang global variable and golang global variables: package-level var, global variable declaration golang, golang globals and go global variables, golang global variables across packages with exported names, global struct golang, shadowing, init order, and why globals need sync for goroutines.

Published

Updated

Read time 5 min read

Reviewed byDeepak Prasad

Golang global variables: package scope, exports, structs, and pitfalls

People searching golang global variable, golang global variables, go global variable, go global variables, global variable golang, global variable declaration golang, golang global, or golang globals are usually asking about variables declared outside functions (package scope), how they differ from locals, and how golang global variables across packages are exposed with exported identifiers. This article covers those patterns, adds global struct golang examples, and points to variable scope and accessing variables across packages for related reading.

Tested with Go 1.24 on Linux.


Package-level variables in Go

In Go, “global” almost always means package-level: a var or const declared in the package block (top level of a file, outside any function). Every .go file with the same package clause shares that namespace, so a golang global variable declared in foo.go is visible in bar.go in the same package without an import.

Global variable declaration golang and zero values

A package-level declaration looks like any other var; the name is available throughout the package for the life of the program.

go
package main

import "fmt"

var username string

func main() {
	username = "John Doe"
	fmt.Println("Username is", username)
}
Output

Running the program prints Username is John Doe (the zero value for username before assignment would be "").

Same package, multiple files

You do not need a special keyword to share a golang global variable across files—only the same package name. Splitting var Config into config.go and using it in main.go in the same directory is normal.

Initialization order and init

Package-level variables are initialized before main runs, in dependency order between packages and among files as described in the language spec. Multiple init functions in a package run after variable initialization. If you need cross-variable ordering within one package, prefer explicit initialization in one var block, init, or constructors rather than relying on subtle file order.


Golang global variables across packages

Other packages can only refer to names that are exported (identifier starts with an uppercase letter). The import path is the module path plus subdirectory, not the word package in the file.

Rules of thumb:

  1. Put shared state in a library package (not package main), because main packages are built as commands, not imported as libraries.
  2. Export the name: var Mypath string, not var mypath string, for cross-package access.
  3. In the importer, use importpath.Mypath after importing that path.

Subpackage example (run from a small module on your machine; not Playground-single-file):

go
// util/mypath.go
package util

var Mypath string

func init() {
	Mypath = "/tmp"
}
go
// main.go
package main

import (
	"fmt"
	"example.com/demo/util"
)

func main() {
	fmt.Println("The path is:", util.Mypath)
}

Save under a module (for example go mod init example.com/demo), adjust the import to your module path, then run go run . from the module root. Output shows The path is: /tmp.

For more patterns (including why tiny “global config” packages grow quickly), see how to access variables across packages.


Global struct golang

A global struct golang pattern is simply a package-level variable whose type is a struct (named or anonymous). This is common for default settings or shared registries; keep them small or immutable when you can.

go
package main

import "fmt"

type Server struct {
	Host string
	Port int
}

var DefaultServer = Server{Host: "localhost", Port: 8080}

func main() {
	fmt.Println(DefaultServer.Host, DefaultServer.Port)
}
Output

If you need a pointer, declare var DefaultServer *Server (zero value nil) and assign in init or main—same rules as any other pointer var.


Changing globals, shadowing, and mistakes

Any code in the package can read or write a package-level var, which is why globals make tests and refactors harder than passing parameters or using small structs.

Simple mutation (single goroutine)

go
package main

import "fmt"

var counter int

func main() {
	for i := 0; i < 1000; i++ {
		counter++
	}
	fmt.Println("Counter value is", counter)
}
Output

Running it prints Counter value is 1000.

Shadowing (local hides package-level)

If you declare a new variable with := inside a function using the same name as a package-level variable, the inner name is a different variable for that block—the package-level one is shadowed, not updated.

go
package main

import "fmt"

var language = "No language"

func main() {
	language := "Go language"
	fmt.Println(language)
}
Output

This prints a single line: Go language. The package-level language is never assigned in main; the short variable declaration introduces a local. To assign to the package-level variable, use language = "Go language" (no := on the left) or a different local name.

Pointer globals

A package-level pointer starts at nil until assigned. Dereference only after assignment.

go
package main

import "fmt"

var username *string

func main() {
	name := "John Doe"
	username = &name
	fmt.Println(*username)
}
Output

See the Go pointers tutorial for deeper detail.


Concurrency and golang globals

When multiple goroutines read and write the same package-level variable without synchronization, you get data races and flaky behavior. Prefer not to share mutable globals; when you must, use a mutex, sync/atomic for simple counters, or channels. A loop that only mutates a global from one goroutine (as in the counter example above) is fine; adding go func() around the loop without a lock is not.


Summary

A golang global variable is normally a package-level var or const: visible across all files in that package, initialized before main, and optionally exported for golang global variables across packages. Global variable declaration golang uses the same var syntax as elsewhere; global struct golang is the same idea with a struct type. Locals declared with := can shadow a package-level name, which confuses people who expect assignment. For go global variables shared by goroutines, treat them like any other shared memory: synchronize or avoid mutation.


References


Frequently Asked Questions

1. What counts as a golang global variable?

In Go the usual term is a package-level variable: a var (or const) declared in a .go file outside any function, visible to all code in that package and, if exported, to importers of the package.

2. How do golang global variables across packages work?

Put the variable in a non-main library package and give it an exported name (capital first letter); importers use importpath.VarName. package main is not importable as a library by other packages.

3. How is global variable declaration golang different from inside `main`?

Package-level declarations run during package initialization before main; function-local variables exist only for the call or block and can shadow a package-level name with the same identifier.

4. Can I use a global struct golang style?

Yes: a package-level var of struct type (or a package-level value of a named struct type) is common; prefer small immutable config values or inject dependencies instead of large mutable singletons.

5. Are golang globals safe with goroutines?

Concurrent reads and writes to the same variable without synchronization are a data race; use sync.Mutex, sync/atomic, channels, or avoid shared mutable globals.
Antony Shikubu

Systems Integration Engineer

Highly skilled software developer with expertise in Python, Golang, and AWS cloud services.