How Go Programs Are Structured: Packages, main, and the Toolchain
Go organizes source code into packages, compiles them into object files, and links them into a single static binary.
Search across all documentation pages
Go organizes source code into packages, compiles them into object files, and links them into a single static binary.
Understanding that pipeline helps you place main, reason about imports, and debug build failures without guessing.
main, compiled by the Go toolchain into one executable with no separate runtime to ship.main package, build cache, linking.go build errors, or deciding where init and main belong.go.mod, workspace mode, vendoring, cross-compilation, and the standard library layout.Go source files belong to exactly one package, declared at the top of every file with package foo.
The package name is usually the last segment of the import path (for example import "net/http" uses package http), but it does not have to match the directory name in rare cases like package main.
Only the main package produces an executable.
It must define func main() as the process entry point.
Every other package is a library: it compiles to an archive (.a) that the linker combines into the final binary.
A module is the unit of versioning.
The go.mod file at the repo root names the module path (for example example.com/myapp) and pins dependency versions.
Import paths inside your module start with that module path plus the directory path under it.
Think of the layout like a small factory line:
.go source files --> go compiler --> .a archives --> linker --> single binary
The Go toolchain (go command) orchestrates compile, link, test, and module download.
You rarely invoke gc or ld directly.
When you run go build, the tool resolves the module graph from go.mod, walks imports starting at your main package, and compiles each needed package.
Results land in the build cache under $GOPATH/pkg or the module cache so unchanged packages skip recompilation.
Exported identifiers start with an uppercase letter; lowercase names are package-private.
That rule is enforced by the compiler, not by access modifiers in source.
The linker performs dead code elimination at the function level for unreachable code, which keeps binaries smaller than naively linking every symbol.
// cmd/myapp/main.go - executable entry
package main
import "example.com/myapp/internal/server"
func main() {
server.Run()
}internal/ directories are special: the compiler rejects imports of example.com/myapp/internal/... from outside the example.com/myapp tree.
That gives you a enforced boundary for non-public code.
init functions in a package run before main, in dependency order, and are mainly for registration or one-time setup.
Overusing init makes startup order hard to reason about.
go test compiles test files (*_test.go) in the same package (or package foo_test for black-box tests) and links a test binary that runs Test* functions.
The same package graph rules apply.
Workspaces (go.work) let multiple modules compile together during local development without publishing pseudo-versions.
Use them when you edit a library and its consumer in parallel.
Cross-compilation sets GOOS and GOARCH; Go still emits a static binary by default on most platforms.
CGO-enabled builds link C code and complicate cross-compiles because they need a matching C toolchain.
| Approach | Strength | Weakness | Best Fit |
|---|---|---|---|
| Monorepo, one module | Simple imports, one go.mod | Large modules can slow CI | Small teams, single product |
| Library module + app module | Clear release boundaries | Local replace/workaround needed | Shared libs across products |
internal/ packages | Compiler-enforced privacy | Cannot share with external importers | App-specific helpers |
Vendoring (vendor/) | Reproducible offline builds | Manual or go mod vendor upkeep | Air-gapped or strict audit |
Container images for Go often copy only go.mod, go.sum, and source, run go build -o /app, and ship a scratch or distroless image because the binary needs no JVM or interpreter.
Observability hooks (pprof, trace) compile into the same binary via blank imports or explicit registration in main.
package main and renamed imports (import http "net/http") break that pattern.go run skips the build step - It compiles to a temp binary and runs it; it is not an interpreter.go.mod still records versions.A module is a versioned unit declared in go.mod (often one repo).
A package is a compile unit inside that module (usually one directory of .go files).
In a package named main, typically under cmd/<appname>/main.go in larger projects.
Only one main package is linked per executable.
The module path in go.mod acts as a globally unique prefix so go get can fetch code from VCS hosts like GitHub or your private forge.
It merges compiled package archives, resolves symbols, applies dead code elimination, and writes a native executable for the target OS and architecture.
All init functions in imported packages run first, in dependency order, then main.main runs.
Multiple init functions in one file run in source order.
Import cycles are rejected at compile time.
Refactor shared types into a third package or narrow interfaces to break the cycle.
Go treats .../internal/... import paths as private to the parent tree above internal.
External modules cannot import them.
Pure Go builds are statically linked by default.
Imports of CGO or certain syscall wrappers may pull in dynamic libraries depending on platform and build tags.
Compiled package outputs are keyed by source hashes and flags.
Unchanged packages reuse cached .a files instead of recompiling.
cmd/ holds multiple main packages (CLI tools, workers) while library code stays importable at the module root or under pkg/ by convention.
Go selects the minimum version that satisfies all requirements (MVS).
You see one version in go.mod unless you use replaces for exceptions.
Modules replaced GOPATH for dependency management.
GOPATH still defaults module cache and build artifact locations but you do not put source there for normal module-based work.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026