Core Go Commands

Updated

September 13, 2026

Core Go Commands

The go command is the whole toolchain: run, build, test, format, vet, document, list, env, clean. Learn these nine well and you can ignore the rest for a long time.

Mental model

You type go <verb> in a module directory. The verb looks at go.mod, the .go files in the package, and (for tests) the *_test.go files. Output is deterministic enough to script. None of these verbs need an IDE.

go run is for trying a program. go build is for keeping a binary. go test is how you know the program still works. gofmt / go fmt are how the team shares one layout. go vet catches mistakes the compiler allows. go doc and go list answer questions. go env prints the machine. go clean removes build debris.

Worked examples

Case 1: go run and go build

Save as open.go in a module (go mod init desk if you do not have one):

// open.go
package main

import "fmt"

func main() {
    fmt.Println("desk open")
}

Run:

go run .

Output:

desk open

Build and run the binary:

go build -o desk .
./desk

Output:

desk open

go run throws the executable away. go build -o desk . keeps it. Ship the build, not the run.

Case 2: go test

Two files in the same package. hours.go is the program. hours_test.go is the test. go test compiles them together.

Save as hours.go:

// hours.go
package main

import "fmt"

func openHours() string {
    return "09:00-17:00"
}

func main() {
    fmt.Println(openHours())
}

Save as hours_test.go:

// hours_test.go
package main

import "testing"

func TestOpenHours(t *testing.T) {
    got := openHours()
    want := "09:00-17:00"
    if got != want {
        t.Fatalf("openHours() = %q, want %q", got, want)
    }
}

Run:

go test

Output:

PASS
ok      desk    0.001s

The module path in the ok line matches your go.mod. The time varies. FAIL plus a Fatalf message means the strings did not match. go test does not run main.

Case 3: go fmt and gofmt

gofmt is the formatter. go fmt is a thin wrapper that runs it on packages. Save this as messy.go (it is deliberately ugly, and it still compiles):

// messy.go
package main

import "fmt"

func main() {
    fmt.Println("aligned")
}

The listing above is already formatted. To see the tool work, type a messy copy yourself: extra spaces around Println, tabs mixed with spaces, braces on their own strange lines. Then:

gofmt -w messy.go

-w writes the file in place. Without -w, gofmt prints the formatted source to stdout. go fmt ./... formats every package under the current directory. There is no style debate. The output of gofmt is the style.

Case 4: go vet

Save as badprint.go. The compiler accepts it. go vet does not.

// badprint.go
package main

import "fmt"

func main() {
    ticket := "12"
    fmt.Printf("ticket %d\n", ticket)
}

Run:

go vet badprint.go

Output:

badprint.go:8:21: fmt.Printf format %d has arg ticket of wrong type string

Run it anyway:

go run badprint.go

Output:

ticket %!d(string=12)

The program “works.” The line is garbage. Change %d to %s (or ticket to an int) and go vet is silent. Case 5 of the next chapter writes the fix in full.

Case 5: go doc, go list, go env, go clean

Documentation for a stdlib function:

go doc fmt.Println

Output starts with:

package fmt // import "fmt"

func Println(a ...any) (n int, err error)
    Println formats using the default formats for its operands and writes to
    standard output.

List the package and the module:

go list .
go list -m

Output (module name from go.mod):

desk
desk

Environment:

go env GOVERSION GOMOD GOCACHE

Typical output:

go1.27.0
/home/you/desk/go.mod
/home/you/.cache/go-build

Clean the default binary name after go build (directory desk, no -o):

go build
go clean

go clean removes the executable go build wrote in this directory. It does not delete your .go files. go clean -cache wipes the build cache — leave that alone unless a cache bug is the problem.

The trap

Using go run as the production start command, or wrapping every verb in a Makefile before you can recite them. This program is three lines. The trap is the process around it.

// serve.go
package main

import "fmt"

func main() {
    fmt.Println("would listen on :8080")
}

Run:

go run serve.go

Output:

would listen on :8080

In a container or a service unit, run the binary from go build (or go install). go run rebuilds from source every time, hides compile errors behind a convenience wrapper, and is not how you pin what you shipped.

The other trap: skipping go vet because the compiler was green. Case 4 is the exhibit.

The boring rule

  • go run . while learning. go build -o <name> . when you keep a binary.
  • go test in the package directory. Tests are *_test.go.
  • gofmt -w (or go fmt ./...) on every save. No personal layout.
  • go vet ./... before you call the change done.
  • go doc, go list, go env instead of guessing paths.
  • go clean for leftover binaries. Not -cache as a ritual.

Try this

  1. In the desk module, run go build -o desk . then go run .. Confirm both print the same line.
  2. Break TestOpenHours by changing want. Run go test and read the Fatalf line. Fix it.
  3. Run go doc -all fmt and find Printf. Then go list -f '{{.GoFiles}}' . in a package that has more than one .go file.