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.mod to 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 main package, 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 . or go 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 that go build silently lets through.
  • Commit both go.mod and go.sum to version control, and never hand-edit either file.
  • Run go mod tidy whenever you add, remove, or change an import, and again before committing.
  • Prefer go install example.com/tool@v1.2.3 for installing a specific version of a command-line tool globally, rather than cloning it and running go build by 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> or go doc <package>.<Symbol> for quick, offline reference instead of always reaching for a browser.
  • Use GOOS/GOARCH environment variables to build and smoke-test binaries for other platforms without needing access to a machine running that platform.
  • Keep go.mod‘s go directive 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 a main.go that prints a greeting, and run it three different ways: go run ., then go build followed by executing the binary, then go install followed 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). Try go run main.go and observe the failure, then fix it with go run ..
  • Write a function with a deliberately wrong fmt.Printf format verb (like %d for a string argument). Confirm go build succeeds but go vet ./... reports the mistake, then fix it.

Summary

  • The go command is one binary exposing many subcommands: build, run, install, test, mod, get, fmt, vet, doc, list, env, generate, and clean.
  • Modules, defined by go.mod and locked by go.sum, are the default dependency system since Go 1.16 and replace the legacy GOPATH workflow.
  • go build compiles, go install compiles and places the binary on your PATH, and go run compiles 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 vet and go fmt catch problems go build does not – run them routinely, ideally in CI.
  • GOOS and GOARCH enable native cross-compilation to any supported platform from any host.
  • Never hand-edit go.sum; regenerate it with go mod tidy whenever dependencies change.