New: Try Voli The Bear, Fast package manager (and not only) for Windows
Updated Jun 19, 2026 Edit on GitHub

The Ecosystem & Tooling - Batteries Included

Picking up a new language usually means a second, miserable project: choosing a build tool, a test runner, a formatter, a linter, a package manager - and making them all agree. Go's answer is one of its best features:

The toolbox comes in the box. Install Go and you already have the build tool, the test runner, the formatter, the vetter, and the dependency manager - one command, go, with subcommands.

Each tool is a go <verb>, each does one job, and together they're why an unfamiliar Go project is usually trivial to build, test, and read.

go run and go build - execute vs. produce

go run compiles your code to a temporary binary and runs it immediately - the fast feedback loop while working. go build compiles and keeps the result: a single, self-contained executable you can ship.

📝 Terminology. Go compiles to a single native binary with no separate runtime to install on the target machine - build it once and copy that one file. That's why "deploy a Go service" is often "scp one file," and why Docker images for Go are tiny.

$ go run .
Hello, Missing Manual!

$ go build -o hello .
$ ls -lh hello
-rwxr-xr-x  1 you  staff   2.1M Jun 19 10:22 hello
$ ./hello
Hello, Missing Manual!

go run . compiled the package in the current directory and ran it on the spot, leaving no file behind - perfect for iterating. go build -o hello . did the compile but wrote the result to a file named hello, which we then ran directly. The binary is a couple of megabytes because Go statically bundles everything it needs - no dependencies to chase on the server.

go fmt - the end of formatting debates

go fmt (which runs the gofmt tool) rewrites your code into Go's one true canonical format: tabs, brace placement, spacing, alignment - all non-negotiable. There's nothing to argue over because there are essentially no options.

Why this is a feature, not a constraint. Every other language community has burned years on tabs-vs-spaces, brace style, line length. Go ended the war by decree: one format, tool-enforced, so all Go code - yours, the standard library's, a stranger's on GitHub - looks the same. Code review stops being about style and starts being about substance. Most editors run gofmt on save.

$ cat messy.go
package main
import "fmt"
func main(){
fmt.Println( "hi" )
}

$ gofmt -w messy.go
$ cat messy.go
package main

import "fmt"

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

The original file had crooked indentation, a cramped import, and stray spaces inside the parentheses. gofmt -w (-w writes changes back to the file) rewrote it into the canonical layout - blank line after the package clause, tab indentation, tidy spacing. One correct answer, and the tool applied it.

💡 Key point. Don't hand-format Go and don't argue about style in review - let gofmt do it. "Run it through gofmt" is the entire formatting policy of the Go world.

go vet - catches suspicious code the compiler allows

go vet is a built-in static analyzer that flags code that compiles fine but is probably a bug - the classic example being a Printf whose format verbs don't match its arguments.

$ cat main.go
package main

import "fmt"

func main() {
	name := "Ada"
	fmt.Printf("hello %d\n", name) // %d expects an int, but name is a string
}

$ go vet .
# example/hello
./main.go:7:2: fmt.Printf format %d has arg name of wrong type string

The code compiled - Printf takes any arguments - but it's wrong: %d is for integers and name is a string, so at runtime you'd get garbled output like hello %!d(string=Ada). go vet spotted the mismatch before you ran it - the cheap safety net for bugs the compiler is too permissive to reject.

go test - testing is built in

Go's test runner is part of the toolchain. Write tests in files named *_test.go, in functions named TestXxx(t *testing.T), and run them with go test. No framework to install, no config file.

Given a function and a test beside it:

// math.go
package mathx

func Double(n int) int { return n * 2 }
// math_test.go
package mathx

import "testing"

func TestDouble(t *testing.T) {
	got := Double(3)
	if got != 6 {
		t.Errorf("Double(3) = %d; want 6", got)
	}
}
$ go test ./...
ok  	example/mathx	0.003s

go test ./... found every test in the module (./... means "this directory and all subdirectories"), compiled them with their packages, ran each TestXxx, and reported ok because nothing called t.Errorf. A test "fails" by reporting through t - no assert library, just a plain if and t.Errorf when reality doesn't match expectations.

📝 Terminology. ./... is Go's wildcard for "the current directory and everything beneath it recursively." You'll use it constantly - go test ./..., go vet ./..., go build ./....

go mod - dependencies, the modern way

A module is the unit Go uses to track your project and its dependencies, defined by a go.mod file at the root. go mod init creates it, go get adds a dependency, and go mod tidy syncs go.mod to exactly what your code actually imports.

$ go mod init example/hello
go: creating new go.mod: module example/hello

$ go get github.com/google/uuid
go: added github.com/google/uuid v1.6.0

$ go mod tidy
$ cat go.mod
module example/hello

go 1.25

require github.com/google/uuid v1.6.0

go mod init created a go.mod declaring your module's name. go get downloaded the uuid package, recorded the exact version (v1.6.0), and wrote a go.sum file with cryptographic checksums so future downloads are verifiably identical. go mod tidy reconciled go.mod with your real imports - adding what you started using, removing what you stopped. (Project layout and go.mod in depth: Phase 5; this is the tooling side.)

💡 Key point. go mod tidy is the one to run before you commit - it guarantees go.mod/go.sum exactly match your imports, so a fresh clone builds with no surprises.

The standard library - more "batteries" than you expect

Go's standard library is unusually broad and production-grade. A real HTTP server, JSON encoding/decoding, cryptography, file and OS access, regular expressions, templating, and the testing tools above - all in the box, no third-party packages required.

An HTTP server in the standard library alone:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello from net/http!")
	})
	http.ListenAndServe(":8080", nil)
}
$ go run . &
$ curl localhost:8080
Hello from net/http!

With nothing but net/http, we registered a handler for / and started a real web server on port 8080 - no framework, no dependencies in go.mod. The curl hit it and got the response. This is why a lot of Go services run on the standard library for a long time before reaching for anything external. (Popular web frameworks built on top of this are signposted in Phase 18.)

golangci-lint - the one tool worth adding

The toolchain covers almost everything, but the community standard for deeper linting is golangci-lint - a fast runner bundling dozens of linters (including go vet and the errcheck "you ignored an error" check from Phase 7) behind one command.

$ golangci-lint run ./...
main.go:12:2: Error return value of `f.Close` is not checked (errcheck)
		f.Close()
		^

golangci-lint run ./... ran its whole battery of linters in one pass and flagged an unchecked error - exactly the silent-failure trap from the errors phase. It's the single external tool most Go teams install; everything else they need already shipped with Go. (Install from the official instructions at golangci-lint.run - versions and install methods change, so check the source rather than copying a command that may be stale.)

Recap

  1. go run / go build - run on the spot vs. produce a single self-contained binary you can ship.
  2. go fmt - one canonical format, enforced; it ends style debates so review is about substance.
  3. go vet - flags compiles-but-probably-wrong code (like Printf verb mismatches).
  4. go test ./... - built-in test runner; tests are TestXxx(t *testing.T) in *_test.go, no framework needed.
  5. go mod - init, get, and especially tidy to keep dependencies exactly matching your imports.
  6. Standard library + golangci-lint - batteries like net/http and encoding/json are built in; golangci-lint is the one external tool most teams add.

You've got the language and the toolbox. What's left is a handful of conventions and gotchas that bite everyone once.


← Phase 7: Errors & I/O · Phase 9: Idioms & Common Gotchas →

Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.

Check your understanding 2 questions

1. `go fmt` (gofmt)...

2. Go's testing is...