Packages, Imports and Initialization

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Go 1.27 (go1.27.2), checked on 2026-10-18. Every runnable program below was vetted with go vet, compiled and run with that toolchain, and the output (including compiler errors) is the real output. A package is Go’s unit of code organization, compilation and encapsulation: a directory of .go files that share a package name, import other packages by path, and expose a public API through capitalized identifiers. This page explains how packages map to directories, the import forms, why import cycles are forbidden, how internal/ enforces boundaries, in which order package-level variables and init functions run, and how to lay out a real module such as shelf. The module-level concepts (versions, go.mod, dependencies) are on Modules and Dependency Management; the identifier and scope rules are on Program Structure and Lexical Elements.

Packages and directories

Every Go source file begins with a package clause. All the files of one package live in one directory, and a directory holds exactly one package (plus, optionally, its external test package, described below). The compiler rejects a directory whose files disagree:

x/a.go:  package catalog
x/b.go:  package store
$ go build ./x
found packages catalog (a.go) and store (b.go) in /work/shelf/x (path shortened)

The rules in brief:

  • A package is the set of .go files in one directory with the same package name. Files are compiled together, and every package-level identifier declared in any of them is visible in all of the others, without any import.

  • The package name is the name used by importers to qualify identifiers (catalog.Book). The import path is a string that locates the package ("example.com/shelf/catalog"). By convention the last element of the path is the package name, but the language does not require it, which is the source of one of the pitfalls shown later.

  • The package name main is special: a main package with a func main() is built into an executable (a command). A package named main cannot be imported by another package:

    m2.go:2:8: import "example.com/shelf/m" is a program, not an importable package
  • Files whose names end in _test.go belong to the package under test, or to an external test package whose name is the package name plus _test (for example package catalog_test). The external package may live in the same directory and imports the package under test like any other client, so it can only use its exported API. See Testing.

Build constraints (//go:build) choose which files of a directory take part in a build; see The go Command, Builds and Cross-Compilation.

The shelf layout

The running shelf example has one library package, one helper package that must stay private, and two commands. The tool can print the real import edges with go list:

go list -f '{{.ImportPath}} ({{.Name}}) -> {{join .Imports " "}}' ./...
example.com/shelf/catalog (catalog) -> errors fmt strings sync
example.com/shelf/cmd/shelf (main) -> example.com/shelf/catalog example.com/shelf/internal/textutil fmt
example.com/shelf/cmd/shelfd (main) -> encoding/json example.com/shelf/catalog log/slog net/http
example.com/shelf/internal/textutil (textutil) -> strings
flowchart TD subgraph mod["module example.com/shelf (go.mod)"] cli["cmd/shelf
package main"] srv["cmd/shelfd
package main"] cat["catalog
public API: Book, ISBN, Store"] subgraph int["internal/ (importable only inside the module)"] tu["internal/textutil
Normalize"] end end cli --> cat cli --> tu srv --> cat cat -.-> std1["errors, fmt, strings, sync"] srv -.-> std2["encoding/json, log/slog, net/http"] tu -.-> std3["strings"] ext["other modules"] -. "may import" .-> cat ext -. "compile error" .-> tu

Solid edges are imports between packages of the module, dotted edges are imports of the standard library, and the last two edges show what other modules may and may not import. Edges only ever point from commands towards libraries and from libraries towards helpers: the graph has no cycle, and that is a requirement, not a style choice.

Import paths and import declarations

An import declaration names the packages whose exported identifiers the file uses. It is file-scoped: each file lists its own imports, and an import in one file is not visible in the others, even in the same package.

package main

import (
	"fmt"
	"math"
	str "strings"

	"example.com/shelf/greet"
	_ "example.com/shelf/greet"
	. "unicode/utf8"
)

func main() {
	fmt.Println(str.ToUpper(greet.Hello("shelf")), math.Sqrt(16), RuneCountInString("héllo"))
}
greet: init
HELLO, SHELF 4 5

The greet package here has an init function that prints greet: init. It ran once, although the package was imported twice (a duplicate import of the same path is legal, but pointless). The three non-default forms are:

Form Effect Typical use

str "strings"

Binds the package to the local name str in this file only

Resolving a name clash between two imports; renaming a package whose name differs from the last path element; shortening a long name

_ "path"

Imports the package only for its side effects: it is initialized (its init functions and variable initializers run) but none of its identifiers is accessible

Registering database drivers and image decoders; see the registration example below

. "path"

Makes the package’s exported identifiers visible in the file without a qualifier (RuneCountInString instead of utf8.RuneCountInString)

Almost never; it hides where a name comes from. A few external test packages use it deliberately

Rules worth knowing:

  • The import path is a string literal. For the standard library it is a short path ("net/http"); for everything else it starts with the module path from go.mod ("example.com/shelf/catalog"). Relative paths such as "./catalog" are not allowed in module mode.

  • The qualifier is the package name declared in the imported package’s clause, not necessarily the last path element. math/rand/v2 is used as rand; a package at example.com/shelf/y that declares package lib is used as lib, and the following program does not compile:

    package main
    
    import "example.com/shelf/y"
    
    func main() { y.Run() }
    ./z.go:3:14: undefined: y

    Import with an explicit alias (y "example.com/shelf/y") or, better, name the package after its directory.

  • An imported package that is never used is a compile error, which keeps dependency lists honest:

    cmd/a/main.go:5:2: "os" imported and not used
  • Referring to an unexported identifier of another package is also an error, at the qualified use and not at the import:

    cmd/a/main.go:11:42: undefined: greet.secret
  • gofmt sorts imports inside blank-line separated blocks (conventionally standard library first, then the rest); goimports also adds and removes them. See Formatting, vet, fix and Static Analysis.

  • import "C" is not an ordinary import; it turns on cgo (cgo and C Interoperability).

  • A local variable named like an imported package (url, path) shadows the import inside its scope, a classic cause of confusing errors.

Import cycles

Package A cannot import package B if B (directly or through other packages) imports A. The compiler needs packages to be built bottom-up, and Go chose to forbid cycles rather than support them. If shelf tried to have catalog ask a loans package whether a book is lent while loans uses catalog.Book, the go command stops before compiling anything:

package example.com/shelf/catalog
	imports example.com/shelf/loans from catalog.go
	imports example.com/shelf/catalog from loans.go: import cycle not allowed

The error lists the cycle in order, so it is easy to read. A cycle is a design signal: two packages that need each other are usually one package, or share a concept that belongs in a third one. The usual repairs are:

  1. Merge the two packages, when they are really one cohesive unit.

  2. Extract the shared types into a lower-level package that both import (for example, move Book into catalog and have loans depend on it, while catalog depends on nothing in loans).

  3. Invert the dependency with an interface. catalog declares the small interface it needs (type LoanChecker interface { Active(isbn ISBN) bool }) and loans provides a type that satisfies it, with no import of catalog in the lender’s direction. Interfaces are satisfied implicitly, so the lower package never has to name the higher one; see Interfaces.

  4. Pass a function value instead of importing the package that supplies it, for hooks and callbacks.

Cycles are detected between packages, not between files. An external test package catalog_test may import loans even when loans imports catalog, because nothing imports catalog_test.

Exported and unexported identifiers

Go has no public or private keyword. An identifier is exported when its first character is an upper-case Unicode letter and it is declared at package level (or is a field or method name). Everything else is visible only inside its own package. This applies uniformly to types, functions, variables, constants, struct fields and methods:

package greet

func Hello(name string) string { return "hello, " + name } // exported

func secret() string { return "x" } // unexported: usable only inside package greet

Consequences:

  • The unit of encapsulation is the package, not the type. Two types in one package can read each other’s unexported fields.

  • Exported struct fields are visible to encoding/json and other reflection-based packages; unexported ones are ignored (Structs and Embedding).

  • Renaming an exported identifier breaks every importer; keep the exported surface small.

Internal packages

Some packages are public inside a module but should never become part of its API. The go command enforces one rule for that: a package path that contains the path element internal can be imported only by code rooted at the parent of the internal directory. For shelf, example.com/shelf/internal/textutil can be imported by anything below example.com/shelf/, and by nobody outside it. A second module that tries gets this error from the go command, not from the compiler, so it appears before any compilation:

package example.com/app
	main.go:6:2: use of internal package example.com/lib/internal/secret not allowed

The rule applies at any depth: a/b/internal/c is importable from a/b and below, not from a. The standard library uses it heavily (crypto/internal/boring). internal lets a library change helpers without a major version bump.

Package initialization

A program starts by initializing its packages and then calling main.main. The language specifies the whole process, and the result is deterministic for a given set of sources.

  1. Packages are initialized one at a time. A package is initialized only after all the packages it imports are initialized, so a dependency graph (the one shown above) is walked leaves first. A package imported by several others is initialized once.

  2. Inside a package, package-level variables are initialized first, and then the init functions run.

  3. For variables, the rule is dependency analysis: repeatedly select the earliest variable in declaration order that is ready, meaning it has no initialization expression, or its expression refers to no uninitialized variable of the package, directly or through the functions and methods it calls. The declaration order is the order in which files are presented to the compiler (the go tool sorts them by file name), and within a file the textual order.

  4. Each init function then runs in the order it appears in the files presented to the compiler. A file may contain several init functions, and one package may have many files with init functions.

  5. Finally, main.main runs.

The following program contains every piece of that: a main package whose variables depend on each other in a different order than they are declared, two init functions, and two imported packages that print as they initialize.

package main

import (
	"fmt"

	"example.com/shelf/initb"
)

var (
	a = c + b // depends on c and b
	b = f()   // depends on d
	c = f()   // depends on d
	d = 3
)

func f() int {
	d++
	fmt.Println("f called, d =", d)
	return d
}

func init() { fmt.Println("main: init 1") }

func main() {
	fmt.Println("main: a, b, c, d =", a, b, c, d, "initb.X =", initb.X)
}

func init() { fmt.Println("main: init 2") }

Package initb imports inita; inita has two files, a.go and b.go, with a variable initialized by a function that prints, and three init functions (one in a.go, two in b.go). The output:

var inita.B
inita: init in a.go
inita: init in b.go (1)
inita: init in b.go (2)
initb: init, X = 2
f called, d = 4
f called, d = 5
main: init 1
main: init 2
main: a, b, c, d = 9 4 5 5 initb.X = 2

Reading the output: inita finished before initb because initb imports it; inside inita the variable initializer ran before any init although it is declared in the second file, and a.go came before b.go by file name. In main, a is declared first but depends on b and c, which depend on d, so the order is d, b, c, a (hence b is 4, c is 5 and a is 9). Both init functions ran after all variables and before main.

The dependency analysis is purely textual: it follows references to variables and calls to functions and methods, but not calls through interface values or function-valued variables, which the compiler cannot resolve. An initialization that has a reference cycle is rejected:

package main

import "fmt"

var total = count()

func count() int { return limit + 1 }

var limit = total * 2

func main() { fmt.Println(total, limit) }
cyc/c.go:5:5: initialization cycle for total
	cyc/c.go:5:5: total refers to count
	cyc/c.go:7:6: count refers to limit
	cyc/c.go:9:5: limit refers to total

The error shows the complete chain, including the hidden step through the function count. Package-level initialization cycles are also reported by go vet, whose output differs only in the prefix.

The init function

func init() is a function with no parameters and no results that the runtime runs automatically. It can appear any number of times in a package, including several times in one file. It is not a declared identifier: it cannot be called, referenced or assigned.

package main

func init() {}

func main() { init() }
./init_ref.go:5:15: undefined: init

An init function is legitimate for work that cannot be expressed as a variable initializer, such as validating or repairing program state before main runs, or registering a type in a registry. Its costs are the reason most style guides advise against it:

  • It runs for every program and test binary that imports the package, even when none of its code is used, so slow work makes every import slow.

  • It cannot return an error; the only reactions are to panic or log, which callers cannot recover from or test.

  • It hides dependencies and encourages mutable package-level state, so reading main does not explain what the program does and tests interfere with each other.

The preferred alternatives are an explicit constructor that returns an error (catalog.New(opts)), a variable initializer that calls a pure function, and lazy initialization on first use. The standard library gives the last one a one-line form: sync.OnceValue computes a value the first time it is needed and caches it. See Sync and Atomics for Once, OnceFunc, OnceValue and OnceValues.

Registration and blank imports

The one widespread, legitimate use of init is registration: a package registers an implementation in a registry owned by another package, and the program selects it by importing the implementation for its side effects. database/sql drivers, image decoders and crypto hashes work this way. The program below decodes a one-pixel PNG without importing a PNG decoder:

package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"image"
	// _ "image/png" // uncomment: the blank import runs png's init, which registers the decoder
)

const tinyPNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="

func main() {
	raw, _ := base64.StdEncoding.DecodeString(tinyPNG)
	cfg, format, err := image.DecodeConfig(bytes.NewReader(raw))
	fmt.Println(cfg.Width, cfg.Height, format, err)
}
0 0  image: unknown format

With the import line _ "image/png" enabled, the same program prints:

1 1 png <nil>

Source: image package

The same pattern with a database driver reads import _ "github.com/lib/pq" in the program’s main package, followed by sql.Open("postgres", dsn); this is shown here as an illustration of the form only, since it needs a third-party module and a server (the database/sql page has the complete flow, see database/sql). Two points matter:

  • Put the blank import in the main package (or a small package imported only by main), not in a library, so that the choice of implementation belongs to the program and not to a dependency.

  • Keep the registering init trivial (one call to a Register function). A driver whose init opens connections is a bug.

Order between unrelated packages

The language leaves the order of unrelated packages to the toolchain. Since Go 1.21 the specification describes it: list all packages sorted by import path and repeatedly initialize the first one whose imports are all initialized. Code must rely on the import graph only, never on that tie-break. There is no "deinit": use defer in main, signal.NotifyContext and explicit Close methods.

Organizing a module

The official guidance lists a few shapes and lets the module grow from one to the next: a basic package (go.mod plus .go files in the root, the import path is the module path); a basic command (package main in the root); a package or command with supporting packages under internal/; multiple packages in top-level directories; multiple commands under cmd/<name>/; and a server project, where cmd/ holds the commands and internal/ almost everything else, because nothing outside the module should import the code.

shelf is a multiple-commands layout (cmd/shelf, cmd/shelfd) whose shared code is the catalog package plus internal/textutil, as the diagram above shows, so a user can run go install example.com/shelf/cmd/shelf@latest and import example.com/shelf/catalog from their own programs.

Guidelines that follow from the language rules and from the official layout page:

  • Start small. One package in one directory is a complete, idiomatic module. Split when a clear boundary appears, not in advance; a premature pkg/, models/, controllers/ and utils/ skeleton recreates the cycles and the grab-bag packages that Go avoids.

  • Group by what the code provides, not by its kind. catalog is a good package; models, util, common or helpers collect unrelated code and become the dependency hub.

  • Give each command its own cmd/<name>/ directory and keep main thin (flags, wiring, a call into library code); see Command-Line Programs.

  • Use internal/ by default for what is not API, and promote a package out when you commit to supporting it.

  • The module root is the directory with go.mod; a nested directory with its own go.mod is a different module (Workspaces, Versioning and Publishing).

  • The popular golang-standards/project-layout repository (/pkg, /internal, /cmd, /api, /web…​) is a community convention. It is not an official standard, the layout page above is the reference. The pkg/ directory in particular adds a path element without adding meaning.

Package naming

A package name is part of every call site (catalog.NewMemStore), so it carries meaning that the exported names need not repeat. The Go blog’s package-naming article and the Code Review Comments page converge on a short list:

  • Short, lower case, no underscores or mixed case: catalog, textutil, httputil. Not Catalog, text_util or textUtil.

  • Singular nouns named after what they provide (catalog, not catalogs, types or models).

  • Avoid stutter. Inside catalog, use catalog.Store and catalog.NewMemStore, not catalog.CatalogStore.

  • Match the last element of the import path, with the exception of major-version suffixes: the package at example.com/shelf/v2/catalog is still catalog, and math/rand/v2 declares package rand.

  • Do not shadow standard packages or common variables (url, string, time), and avoid generic suffixes such as utils. Test packages append _test.

Where the books are outdated

  • GOPATH and remote packages. Vivien (ch. 1 and ch. 6) teaches packages from the GOPATH workspace, with code under $GOPATH/src and go get as the way to fetch and build remote packages, and Bodner (ch. 1 and ch. 9) still discusses the GOPATH-era relationship between repositories and packages while presenting modules. Since Go 1.16 module mode is the default, an import path is resolved through go.mod, and go get only edits dependencies (installing a program is go install path@version). The package and import rules on this page are the same in both worlds, but the directory a package comes from is now determined by the module, not by a workspace layout.

  • Initialization order. Vivien (ch. 6) describes initialization as the spec did at the time. Go 1.21 made the algorithm precise, including the sorted-by-import-path tie-break between unrelated packages.

  • Vendoring. A vendor/ directory is part of module mode (go mod vendor, -mod=vendor), see Modules and Dependency Management.

Version notes

Release Change relevant to this page

Go 1.4 / 1.5

The internal import restriction introduced for the standard library (1.4) and made available to all repositories (1.5)

Go 1.11 / 1.16

Modules introduced (1.11) and enabled by default (1.16); GOPATH mode is legacy

Go 1.21

Package initialization order specified precisely (sorted by import path, dependency-driven)