Irbisa · Cheatsheet

Modules, Build & Tooling

go.mod and MVS, build tags, embed, ldflags, linters, static binaries

Middle Developer · 23 questions

  1. 01

    What are GOROOT and GOPATH, and why does GOPATH matter much less since modules?

    GOROOT is where the Go toolchain itself lives: the compiler, the go command and the standard library.

  2. 02

    What is the difference between a package and a module in Go?

    A package is a directory of .go files with the same package clause: the unit you import and the compiler builds.

  3. 03

    What do go.mod and go.sum each hold, and which of them decides the dependency versions you build?

    go.mod decides: it declares the module path, the Go version and a minimum version for each dependency.

    // go.mod: which versions are builtmodule github.com/acme/billing go 1.25 require (…
  4. 04

    What does go mod tidy do, how is go get different from editing go.mod by hand, and what does go mod verify check?

    go mod tidy makes go.mod and go.sum match your imports: it adds missing requirements and removes unused ones.

  5. 05

    What does // indirect on a require line in go.mod mean, and why does a modern go.mod list so many of them?

    // indirect marks a module that none of your own packages import: it is in go.mod because a dependency needs it, or because you raised its version on purpose.

    // go.mod of a go 1.17+ module: two require blocksmodule github.com/acme/billing go 1.25 require (…
  6. 06

    How do you install a Go command-line tool such as mockgen today, and why does go get no longer produce a binary?

    Use go install <package>@<version>.

    # wrong since Go 1.18: adds a requirement to go.mod, builds nothing$ go get go.uber.org/mock/mockgen$ mockgen -versionzsh: command not found: mockgen # right: build a pinned version into $GOBIN, ignoring the current go.mod…
  7. 07

    What does go generate do, why does go build never run it, and should the generated files be committed?

    go generate runs the commands written in //go:generate comments, in each file's directory, to write Go source such as mocks, String methods or sqlc queries.

    package order //go:generate go tool stringer -type=Status type Status int…
  8. 08

    What do gofmt, go vet, staticcheck and golangci-lint each catch, and how do you run them in CI?

    gofmt fixes layout, go vet finds code that compiles but is almost certainly wrong, staticcheck adds deeper bug and simplification checks, and golangci-lint runs those and many more in one pass with one config.

    # .golangci.yml (golangci-lint v2)version: "2" linters:  default: standard   # errcheck, govet, ineffassign, staticcheck, unused  enable:…
  9. 09

    How do you document Go code, and what do doc comments and go doc look like?

    A doc comment is a plain // comment directly above an exported declaration, starting with the name it describes.

    // Package billing charges customers and issues invoices.package billing import "errors" // ErrDeclined is returned when the card is refused.…
  10. 10

    Given these requirements, which version of golang.org/x/net gets built, and why does Go need no lock file to keep it that way?

    // your go.mod (without the // indirect lines tidy would add)require (	github.com/acme/kit v1.3.0	github.com/acme/api v0.9.0) // kit v1.3.0's go.mod:  require golang.org/x/net v0.28.0// api v0.9.0's go.mod:  require golang.org/x/net v0.31.0 // newest x/net on the proxy today: v0.45.0

    v0.31.0. Go uses Minimal Version Selection: every require line is a minimum, and the build takes the highest minimum anyone states, never a newer release just because it exists.

  11. 11

    Why does a v2 module need /v2 in its module path, and what goes wrong if you tag v2.0.0 without changing it?

    This is semantic import versioning: from v2 on, the major version is part of the module path (module github.com/acme/lib/v2) and of every import, because a breaking release is treated as a different module.

    // the library's go.mod, from v2 on://   module github.com/acme/lib/v2 package main import (…
  12. 12

    Your library works around a broken dependency with replace in its go.mod, but services that import the library still get the broken version. Why?

    replace and exclude apply only in the main module, the one being built.

  13. 13

    How do you make Go fetch private modules from your company Git, and how do GOPROXY, GOPRIVATE and vendoring fit together?

    Set GOPRIVATE to your private path prefixes, for example git.corp.example/*.

    # wrong: a private path goes to the public mirror and checksum DB$ go get git.corp.example/team/lib@v1.2.0verifying module: git.corp.example/team/lib@v1.2.0: reading https://sum.golang.org/lookup/...: 410 Gone # right: one variable covers GONOPROXY and GONOSUMDB for these prefixes$ go env -w GOPRIVATE='git.corp.example/*,github.com/acme/*'…
  14. 14

    What does the go line in go.mod control, and how do the toolchain line and GOTOOLCHAIN decide which Go actually builds the code?

    The go line sets the language version the module's code is compiled with and, since Go 1.21, the minimum Go release allowed to build it.

    // go.modmodule github.com/acme/billing go 1.24.0          // language version + minimum toolchaintoolchain go1.25.3 // preferred toolchain when this is the main module…
  15. 15

    How do you pin dev tools like mockgen or stringer for the whole team: the old tools.go file or the Go 1.24 tool directive?

    Since Go 1.24, use the tool directive: go get -tool pkg@version records the tool in go.mod, and go tool <name> runs exactly that version.

    # Go 1.24+: record the tool in go.mod$ go get -tool go.uber.org/mock/mockgen@v0.6.0 # go.mod now containstool go.uber.org/mock/mockgen…
  16. 16

    How do you lay out a Go service repository, and what does the internal directory enforce?

    Keep it flat and grouped by domain: one cmd/<binary> directory per executable with a thin main, and the rest of the code under internal/.

    github.com/acme/billing/├── go.mod├── cmd/│   ├── api/main.go        // one directory per binary: config + wiring only│   └── worker/main.go└── internal/              // importable only from github.com/acme/billing/...…
  17. 17

    Why does Go forbid import cycles, and how do you break a cycle between two of your packages?

    Go compiles and initialises packages one at a time in dependency order, and a cycle leaves no valid order, so import cycle not allowed is a build error.

    package store import (	"context"	"database/sql")…
  18. 18

    What are build constraints (//go:build, _linux.go suffixes), and how do you cross-compile a Go binary for another OS and architecture?

    A build constraint decides whether a file is part of the build for the current GOOS, GOARCH, Go version and -tags.

    // file: internal/store/pg_integration_test.go//go:build integration package store import "testing"…
  19. 19

    Built with -ldflags="-X main.version=1.4.0 -X main.commit=abc -X main.buildTime=2026-10-14 -X main.env=prod" and no APP_ENV. What does it print?

    package main import (	"fmt"	"os") const commit = "none" var version = "dev"var buildTime stringvar env = os.Getenv("APP_ENV") func main() {	fmt.Printf("%s %s %s %q\n", version, commit, buildTime, env)}

    It prints 1.4.0 none 2026-10-14 "".

  20. 20

    How does govulncheck differ from a version-matching dependency scanner, and what does it check?

    govulncheck reports a vulnerability only when your code can actually reach the vulnerable function, using the call graph and the Go vulnerability database.

  21. 21

    The same commit builds on every laptop but fails in CI with no required module provides package, and the repo has a git-ignored go.work. What is going on?

    go.work changes the module graph on the machine that has it: its use lines make the listed directories main modules, so laptops resolve example.com/acme/lib from the sibling directory.

    // go.work at the repo root: git-ignored here, which is the whole buggo 1.25 use (	./api	./lib…
  22. 22

    Two CI runs of the same commit produce binaries with different hashes. What makes a Go build reproducible, and how does a binary record how it was built?

    A Go build is deterministic given the same source, dependencies, toolchain, flags and environment.

    # same commit on two machines: the hashes should match$ CGO_ENABLED=0 go build -trimpath -o app ./cmd/app$ sha256sum app # what the binary says about itself$ go version -m app…
  23. 23

    What is profile-guided optimisation (PGO) in Go, what does it actually optimise, and how do you roll it out for a service?

    PGO feeds a CPU profile from production into the compiler, which then optimises the code that is actually hot.

    # 1. CPU profiles from production, under real traffic$ curl -o cpu-1.pprof 'http://api-1:6060/debug/pprof/profile?seconds=30'$ curl -o cpu-2.pprof 'http://api-2:6060/debug/pprof/profile?seconds=30' # 2. merge them so one instance or one spike does not dominate$ go tool pprof -proto cpu-1.pprof cpu-2.pprof > cmd/api/default.pgo…