Go Command Reference
The go command is the single entry point for the entire Go toolchain: one binary that compiles your code, runs it, tests it, formats it, checks it for suspicious patterns, and manages your dependencies. Instead of juggling a separate compiler, linker, package manager, formatter, and test runner the way you might in other ecosystems, Go bundles all of it behind one program you already installed alongside the language itself. You will type go commands dozens of times a day as a Go developer, so knowing the full command surface – not just go run – is essential to working efficiently.
Overview: How the Go Toolchain Works
Every Go installation ships a single executable named go (found on your PATH after installing Go). Running it with no arguments prints a summary of subcommands; running go help <command> prints detailed help for one of them. The go tool is itself written in Go and lives in the standard distribution – it is not a shell script wrapping other programs. Each subcommand (build, run, test, get, mod, vet, fmt, doc, install, list, env, generate, clean, version) is a distinct piece of functionality, similar in spirit to how git exposes git commit, git push, and git log from one binary.
Since Go 1.16, module mode is the default and the old GOPATH workflow, where all source had to live under one global workspace, is effectively retired. A module is defined by a go.mod file at the root of your project, created with go mod init <module-path>. It records the module’s import path, the minimum Go version it requires, and the exact versions of every dependency it needs. A companion go.sum file stores cryptographic checksums of every dependency’s contents so a build is reproducible and cannot be silently altered by a compromised package host. Commit both files to version control, but almost never hand-edit either one – let go get, go mod tidy, and go mod download maintain them for you.
The toolchain keeps two caches on disk. The module cache holds the downloaded source of every dependency version you have ever built (its location is reported by go env GOMODCACHE). The build cache holds compiled package objects keyed by a hash of their inputs (reported by go env GOCACHE). The build cache is why a second go build of an unchanged project is nearly instant – the compiler skips recompiling anything whose inputs have not changed. go env also lets you inspect or set variables like GOOS and GOARCH (target operating system and CPU architecture, used for cross-compilation), GOPROXY (where modules are fetched from), and CGO_ENABLED.
It helps to separate three commands beginners often confuse. go build compiles the named packages and, for a main package, writes an executable into the current directory (or wherever -o points); for a non-main package it only verifies compilation and discards the output, which is a fast way to check correctness. go install does the same compilation but places the resulting binary into $GOBIN, which is the right choice for installing command-line tools you want on your PATH. go run is essentially build followed by immediately executing the result from a temporary location and then deleting it – great for quick iteration, not for shipping a binary.
Syntax
Every invocation follows the same general shape:
go <command> [-flags] [arguments]
| Command | What it does |
|---|---|
go run |
Compiles and immediately executes source files without leaving a binary behind |
go build |
Compiles packages into a binary (main packages) or just verifies compilation (library packages) |
go install |
Like build, but installs the resulting binary into $GOBIN |
go test |
Compiles and runs tests found in *_test.go files |
go mod init/tidy/download |
Create a module, synchronize its dependencies, or pre-fetch them |
go get |
Add, upgrade, or downgrade a dependency recorded in go.mod |
go fmt |
Reformats source files to the canonical gofmt style |
go vet |
Statically analyzes code for likely bugs, such as format-string mismatches |
go doc |
Prints documentation for a package, type, or function |
go list |
Prints information about packages or modules |
go env |
Prints or sets toolchain environment variables |
go clean |
Removes build and cache artifacts |
go version |
Prints the installed Go version |
Examples
Example 1: go run and go build
Start with the smallest possible program:
package main
import "fmt"
func main() {
fmt.Println("Hello, Go tooling!")
}
$ go run hello.go
Hello, Go tooling!
$ go build hello.go
$ ./hello
Hello, Go tooling!
Output: both commands print Hello, Go tooling!. go run compiled the file into a temporary directory, executed it, and cleaned up automatically. go build instead left a permanent executable named hello (matching the source file’s base name, or the module name if you build a whole directory) sitting next to your source, which you then ran yourself with ./hello.
Example 2: go test
Real projects need automated tests, not just manual runs. Given a small package:
package main
import "fmt"
func Add(a, b int) int {
return a + b
}
func main() {
result := Add(3, 4)
fmt.Println("3 + 4 =", result)
}
…and a matching test file in the same directory, named with a _test.go suffix so the toolchain knows to exclude it from normal builds and include it only when testing:
package main
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
$ go test -v ./...
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok example.com/mathdemo 0.002s
Output: go test found every function named TestXxx(t *testing.T) in every _test.go file under the current directory tree (./... means “this package and all subpackages”), compiled a temporary test binary that includes both your real code and the test code, ran it, and reported PASS. The -v flag makes it print each test name as it runs; without it, a passing package just prints a single ok line.
Example 3: modules, go get, and cross-compilation
A real project starts with a module and often needs a third-party dependency:
module example.com/mathdemo
go 1.22
$ go mod init example.com/mathdemo
go: creating new go.mod: module example.com/mathdemo
$ go get github.com/google/uuid@v1.6.0
go: added github.com/google/uuid v1.6.0
$ go mod tidy
$ GOOS=linux GOARCH=arm64 go build -o mathdemo-linux-arm64 .
$ go doc fmt.Println
func Println(a ...any) (n int, err error)
Println formats using the default formats for its operands and writes
to standard output.
Output: go mod init wrote the go.mod file shown above. go get resolved the requested version of the uuid module, added it to go.mod, and recorded its checksum in go.sum. go mod tidy then adds any missing requirements and removes unused ones so go.mod exactly matches what your code actually imports. The GOOS=linux GOARCH=arm64 prefix cross-compiles a Linux/ARM64 binary even if you are on a Mac or Windows machine, because the Go compiler, linker, and standard library are themselves written in Go and do not depend on a target-specific C toolchain (as long as CGO_ENABLED=0, which is the default when cross-compiling). Finally go doc printed documentation straight from source comments, without opening a browser.
How It Works Step by Step
When you run go build (or run, install, or test, which all share the same compilation machinery), the toolchain performs roughly these steps:
- Walks upward from the current directory to find the nearest
go.mod, which defines the module root and the package import paths relative to it. - Parses
go.modto learn the module’s own path, its minimum required Go version, and the versions of every direct and indirect dependency. - Builds the full import graph starting from the packages you named, resolving each imported module’s source from the local module cache, downloading anything missing and verifying its checksum against
go.sum. - Type-checks and compiles packages bottom-up: leaf dependencies first, then the packages that import them, and so on up to your own code. Each compiled package is stored in the build cache keyed by a hash of its source and its dependencies’ outputs, so unchanged packages are never recompiled.
- For a
mainpackage, links all the compiled packages into a single executable; for a non-main package, the compiled output is simply cached and discarded from the current directory. - Writes the final binary to the current directory (
go build), to$GOBIN(go install), or executes it immediately from a temporary location before deleting it (go run).
Common Mistakes
Mistake 1: assuming go run only needs one file
If a package’s code is split across multiple files, naming only one of them fails because the compiler cannot see the rest of the package:
$ ls
main.go helper.go
$ go run main.go
./main.go:6:2: undefined: Helper
$ go run .
(works correctly - builds the whole package)
Use go run . (or go run *.go, or go build followed by running the binary) to include every file that makes up the package, not just the one containing func main.
Mistake 2: ignoring what go vet catches
go build only checks that your code is syntactically and type-correct – it does not know that a format verb is wrong, because format strings are just ordinary string values as far as the compiler is concerned:
package main
import "fmt"
func main() {
name := "Gopher"
fmt.Printf("Hello, %d\n", name)
}
This compiles without complaint, but at run time it prints garbage because %d expects an integer, not a string. go vet catches exactly this class of bug through static analysis:
$ go vet ./...
./main.go:7:2: Printf format %d has arg name of wrong type string
The fix is to use the verb that matches the argument’s type:
package main
import "fmt"
func main() {
name := "Gopher"
fmt.Printf("Hello, %s\n", name)
}
Output: Hello, Gopher. Because go build cannot catch this on its own, run go vet ./... as a routine part of your workflow, and let your editor or CI run it automatically.
Mistake 3: hand-editing go.sum, or forgetting go mod tidy
After adding a new import to your code, forgetting to run go mod tidy leaves go.mod out of sync, and builds start failing with errors like no required module provides package .... Some developers try to “fix” this by manually adding lines to go.sum, but go.sum entries are cryptographic hashes generated from the actual downloaded module contents – a hand-written line will simply not match anything and the build will refuse to proceed. The correct fix is always to let the tool regenerate it: run go mod tidy after adding or removing imports, and commit the resulting go.mod and go.sum together.
Best Practices
- Run
gofmt -l .orgo fmt ./...before every commit; most Go style guides and CI pipelines assume code is already canonically formatted. - Run
go vet ./...alongside your build in CI – it catches real bugs thatgo buildsilently lets through. - Commit both
go.modandgo.sumto version control, and never hand-edit either file. - Run
go mod tidywhenever you add, remove, or change an import, and again before committing. - Prefer
go install example.com/tool@v1.2.3for installing a specific version of a command-line tool globally, rather than cloning it and runninggo buildby hand. - Use
go test -race ./...periodically on concurrent code; the race detector finds data races that pass every normal test run. - Use
go doc <package>orgo doc <package>.<Symbol>for quick, offline reference instead of always reaching for a browser. - Use
GOOS/GOARCHenvironment variables to build and smoke-test binaries for other platforms without needing access to a machine running that platform. - Keep
go.mod‘sgodirective reasonably current, but avoid bumping it casually – it sets the minimum Go version required to build your module.
Practice Exercises
- Create a new module with
go mod init practice/greeter, write amain.gothat prints a greeting, and run it three different ways:go run ., thengo buildfollowed by executing the binary, thengo installfollowed by running it from$GOBIN. Confirm all three print the same output. - Split your greeter into two files in the same package (for example, move a helper function into
helper.go). Trygo run main.goand observe the failure, then fix it withgo run .. - Write a function with a deliberately wrong
fmt.Printfformat verb (like%dfor a string argument). Confirmgo buildsucceeds butgo vet ./...reports the mistake, then fix it.
Summary
- The
gocommand is one binary exposing many subcommands:build,run,install,test,mod,get,fmt,vet,doc,list,env,generate, andclean. - Modules, defined by
go.modand locked bygo.sum, are the default dependency system since Go 1.16 and replace the legacyGOPATHworkflow. go buildcompiles,go installcompiles and places the binary on yourPATH, andgo runcompiles and executes without leaving a binary behind.- The build cache and module cache make repeated builds fast and dependency downloads a one-time cost per version.
go vetandgo fmtcatch problemsgo builddoes not – run them routinely, ideally in CI.GOOSandGOARCHenable native cross-compilation to any supported platform from any host.- Never hand-edit
go.sum; regenerate it withgo mod tidywhenever dependencies change.
