skip to content

The go Command

The single go command that compiles, formats, checks and generates code with no external build system involved. Interviewers probe it because Go teams rarely have a build engineer: everyone is expected to know what go build produces and what go vet catches before CI does.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

30

What does a `//go:build linux` line at the top of a Go file do, and where must it appear?

level: juniorimportance: must knowfreq 50%

answer

  1. a whole-file on/off switch
  2. the compiler never sees the file
  3. it has to come before package
  4. blank line keeps it out of the doc comment
  5. below the package clause it is just a comment

basics

~20 s

It is a build constraint. The file is compiled only when the target operating system is Linux, and is skipped entirely otherwise. The line must sit above the package clause, with a blank line after it.

solid answer

~40 s

`//go:build linux` is a build constraint: the go command includes that file in the build only when the target OS is Linux and drops the whole file otherwise, so the compiler never sees any declaration in it. The line must appear before the `package` clause, preceded only by blank lines and other `//` line comments, and is conventionally followed by a blank line so it is not mistaken for the package doc comment. The term after `//go:build` is a build tag: a GOOS value such as `linux`, a GOARCH value such as `arm64`, `cgo`, `race`, a `go1.N` release tag, or any identifier you pass with `-tags`. Written below the package clause it is just an ordinary comment and has no effect; `go vet`'s buildtag check reports that mistake.

code

go · 8 lines
go
//go:build linux

// Package watcher watches paths for changes.
package watcher

func platformName() string {
	return "linux"
}

go deeper

for a junior

Be ready to say in one sentence that the line decides whether the entire file is compiled, and to place it correctly: above the package clause, blank line after it.

for a middle

Explain the mechanics: the expression is boolean over tags, tags have no values, and an unsatisfied file's declarations vanish, which is why platform variants are written as one file per platform defining the same symbol.

for a senior

Show that you treat a silently excluded file as a real risk: it is code nobody compiles and therefore nobody type-checks, and you know go vet's buildtag check catches the misplaced or misspelled directive.

for a principal

Own the rule that every file in the repository must compile in at least one configuration the team actually builds, and be able to say who checks that and how, rather than leaving unbuildable files to rot in the tree.

## What a build constraint is A build constraint is a line comment of the form `//go:build <expression>` that decides, for one whole file, whether the go command hands that file to the compiler at all. It is not a conditional inside the code and it does not guard the lines below it: the unit of selection is the file. If the expression is false for the current build, the file behaves as if it were not in the directory — its functions, types, constants and `init` functions do not exist, and no error is reported for referring to something it would have defined only if nothing else in the package defines it either. The directive is spelled with no space after the slashes. `// go:build linux` is a plain comment and does nothing, which is one of the two classic ways to write a constraint that silently never applies. ## Where it goes The rules are mechanical: - It must appear **before the `package` clause**. - Only blank lines and other `//` line comments may precede it. - It should be **followed by a blank line**, which keeps it from being read as the package doc comment. Anything after the `package` clause is not a build constraint, no matter how it is spelled. The compiler will not complain — the file simply builds everywhere — so the failure is silent. `go vet` ships a `buildtag` analyzer that reports a misplaced `//go:build` comment, which is why running vet catches this class of mistake for free. ## What a build tag is A tag is a bare identifier. The go command sets a fixed set of them for every build: - the target OS, e.g. `linux`, `darwin`, `windows`, `js`, `plan9`; - the target architecture, e.g. `amd64`, `arm64`, `386`; - `unix` on the unix-like operating systems (recognised in `//go:build` expressions by recent Go); - `cgo`, when cgo is enabled and a C toolchain is available; - the compiler name, `gc` or `gccgo`; - `race`, `msan`, `asan` when those instrumentation modes are on; - `go1.1` through the release tag of the toolchain in use, so `//go:build go1.21` means "this toolchain or newer". On top of those, `-tags` adds whatever identifiers you list: `go build -tags integration` makes the term `integration` true. A tag has no value — it is present or absent. There is no `//go:build version==2` and no way to read a tag from Go code; anything that needs a value belongs in a constant declared in the constrained file itself. ## The expression The text after `//go:build` is a boolean expression over tags using `&&`, `||`, `!` and parentheses, exactly as you would write them in Go: ``` //go:build (linux || darwin) && !cgo ``` That file is compiled when the target is Linux or macOS and cgo is off. Because it is a real expression, it is possible to write one that is never satisfiable — `//go:build linux && windows` compiles nowhere — and nothing warns you. ## Why files rather than statements Go has no preprocessor and no `#ifdef`. The idiom is instead: declare the same function or type once per platform in separate files, each guarded by a constraint, plus a shared file with the platform-independent code that calls it. Every build sees exactly one definition, so there is no duplicate-symbol error and no dead branch inside a function. That is also why an unsatisfied constraint is more dangerous than it looks: it does not disable a feature, it removes a definition, and the reader on another OS sees a package that does not obviously contain the code they are reading about. ## The one-liner worth remembering `//go:build ignore` is a convention rather than a feature. Nothing ever sets a tag called `ignore`, so the file is excluded from every build while still sitting in the package directory, where gofmt and human readers can reach it. It is the standard way to keep a standalone helper program next to the package it belongs with without breaking the package build.

  • If the constraint is not satisfied, what happens to the declarations in that file?
    They do not exist for that build. The file is never parsed for compilation, so its types, functions, constants and `init` functions are absent. Other files that reference them fail to compile unless another file in the package supplies the same names under the opposite constraint. That is why platform-specific code is usually written as a set of files that each define the same symbol.
  • Which build tags are true without passing anything to -tags?
    The target GOOS and GOARCH values, `unix` on unix-like systems, `cgo` when cgo is enabled, the compiler name `gc` or `gccgo`, `race`/`msan`/`asan` under those instrumentation modes, and every `go1.N` release tag up to the toolchain in use. `-tags` only adds identifiers on top of that fixed set.
  • Why do people write `//go:build ignore` on a file they keep in the package directory?
    Because no build ever sets a tag named `ignore`, so the file is excluded everywhere. It is the conventional way to park a standalone helper program in a package directory without it joining that package's build. The tag is not special to the toolchain — any never-set identifier would behave the same; `ignore` is just the agreed name.

It is a switch on the file, not on the code inside it. Flip it off and the room is not dark, it is not in the house.

saying these in an interview costs you the question

  • Thinks the constraint only disables the lines below it
  • Puts the //go:build line after the package clause
  • Believes an excluded file still compiles but goes unused
  • Treats build tags as macros that carry a value
  • Writes // go:build with a space after the slashes
open as a page

What is the difference between go build, go run and go install for a main package?

level: juniorimportance: must knowfreq 78%

basics

~20 s

go build compiles a main package and writes the executable into the current directory. go run builds it to a temporary file, runs it, then deletes the binary. go install writes the executable into GOBIN, which defaults to GOPATH/bin.

open as a page

Why is a second `go build` of unchanged Go code so much faster than the first, and where does the toolchain keep what it reuses?

level: juniorimportance: must knowfreq 50%

basics

~20 s

The go command caches compiled package output keyed by a hash of every input. Unchanged code hashes the same, so the second build reuses the stored result instead of recompiling. Running go env GOCACHE prints the directory that holds it.

open as a page

What does a //go:generate comment in a Go source file do, and when does it run?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A //go:generate line is just a comment; no build step reads it. Only the go generate command scans for these lines and runs each named command in the package directory. go build and go test never run them, so the generated output must be committed.

open as a page

What does gofmt do to a Go source file, and why does it have no style options?

level: juniorimportance: must knowfreq 72%

basics

~20 s

gofmt reprints a Go file in one canonical layout: tabs for indentation, blanks for alignment, fixed spacing and brace placement, and import specs sorted inside each existing block. It exposes no style settings, so formatting stops being a review topic.

open as a page

Why does a CI step running `gofmt -l .` pass even when files are unformatted, and how do you make it fail?

level: juniorimportance: must knowfreq 55%

basics

~20 s

gofmt -l lists the paths of files whose formatting differs, then exits 0 whether that list is empty or not, so the step passes. Make the build fail on non-empty output instead, wrapping it with test -z.

open as a page

What does go install example.com/cmd/[email protected] do that go install ./cmd/mytool does not?

level: middleimportance: must knowfreq 62%

basics

~20 s

The version suffix makes go install ignore the current module's go.mod entirely. It downloads that module at exactly v1.4.0, builds its command with that module's own dependency requirements, and installs the binary into GOBIN, changing nothing in your project.

open as a page

What does a //go:embed directive require of the variable it precedes in a Go file?

level: middleimportance: must knowfreq 54%

basics

~20 s

The directive must sit immediately above a package-level variable whose type is a string type, a byte slice, or embed.FS, and the file must import the embed package (a blank import when the type is string or []byte). Patterns are relative to the package directory.

open as a page

Where must a Go doc comment sit for `go doc` to pick it up as documentation?

level: juniorimportance: should knowfreq 48%

basics

~20 s

A doc comment must sit immediately above the declaration it documents, with no blank line between them. Go has no annotation syntax at all: adjacency is the whole rule, so a single blank line turns documentation into an ordinary comment.

open as a page

How do filename suffixes like `_linux.go` or `_amd64.go` constrain when a Go file is compiled?

level: middleimportance: should knowfreq 46%

basics

~20 s

A Go filename ending in a known GOOS or GOARCH value, such as store_linux.go or store_amd64.go, carries an implicit build constraint for that target. The suffix only counts when the value is a real GOOS or GOARCH and something precedes the underscore.

open as a page

How does a `//go:build` expression differ from the legacy `// +build` line in syntax and precedence?

level: middleimportance: should knowfreq 40%

basics

~20 s

//go:build takes one ordinary boolean expression with &&, ||, ! and parentheses. The older // +build line uses spaces for OR, commas for AND, and repeated lines for AND. When a file has both, //go:build wins.

open as a page

What does `go build -tags integration` change for a file whose header is `//go:build integration`?

level: middleimportance: should knowfreq 44%

basics

~20 s

It makes the term integration true for the whole build, so that file is compiled instead of skipped. Without the flag the file is excluded and anything it declares does not exist. A tag is only present or absent; it carries no value.

open as a page

In a Go module, what does the ./... pattern match, and what does go build ./... leave on disk?

level: middleimportance: should knowfreq 50%

basics

~20 s

The ./... pattern matches every package in the current directory and its subdirectories that belongs to the main module. Building several packages at once only checks that they compile: go build discards the executables and writes no files.

open as a page

What does `go build -trimpath` strip from a Go binary, and why does a reproducible build need it?

level: middleimportance: should knowfreq 40%

basics

~20 s

It removes absolute file system paths from the compiled output. Recorded file names become the module path and version, or the package's plain import path, so builds from different checkout directories produce identical bytes instead of embedding /home/alice or /builds/ci.

open as a page

What is a Go package comment, and why do large packages keep it in doc.go?

level: middleimportance: should knowfreq 50%

basics

~20 s

A package comment is the comment block immediately above a package clause, and it is the first thing a reader sees on the package's documentation page. One file should carry it; a long one usually lives in doc.go.

open as a page

Why did a hand edit to a stringer-generated Go file vanish, and where should that code live?

level: middleimportance: should knowfreq 40%

basics

~20 s

Generators rewrite their output file whole, so any hand edit is overwritten the next time go generate runs. That is what the DO NOT EDIT header warns about. Put custom code in a separate file in the same package, or change the source the generator reads.

open as a page

What does go vet's printf analyzer catch in fmt.Printf calls that the compiler cannot?

level: middleimportance: should knowfreq 58%

basics

~20 s

It compares a constant format string against the arguments actually passed: a verb that does not match the argument's type, too few or too many arguments, and formatting directives handed to fmt.Println. The compiler cannot, because Printf takes ...any.

open as a page

What does go.mod's `tool` directive do, and how does it stop tool-version drift between laptops and CI?

level: middleimportance: should knowfreq 36%

basics

~20 s

A tool directive records a helper program's package path in go.mod, so its version resolves from the module graph like any dependency. Running go tool with that program's name builds and runs the pinned version, not whatever binary is on PATH.

open as a page

A build-constrained Go file compiles in no configuration at all. How do you find which files a build actually includes, and why yours is missing?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Ask the go command instead of reading comments: go list -f '{{.GoFiles}}' prints the files in the build and go list -f '{{.IgnoredGoFiles}}' prints those a constraint excluded. Re-run it with the tags and target you expect, then check the constraint, the filename and the placement.

open as a page

Your onboarding script installs Go tools with go install, and every machine ends up with a different build. Why, and how do you fix it?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Almost always the script uses @latest, which resolves to whatever the newest release is the moment each machine runs it, and nothing in the repository records what was installed. Pin an exact version per tool, and fix the install destination explicitly.

open as a page

Two Go builds of the same commit produce binaries with different SHA-256 hashes. How do you make them identical?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Compare the build settings recorded in each binary with go version -m, then pin what differs: the toolchain version, GOOS and GOARCH, CGO_ENABLED, the resolved dependency versions, build tags and GOFLAGS. Add -trimpath so the checkout directory stops leaking in.

open as a page

Your CI runs `go build -a` on every commit for a clean build. What does the flag cost, and is it needed?

level: seniorimportance: should knowfreq 35%

basics

~20 s

The -a flag forces every package to be rebuilt from source, including the standard library, instead of reusing cached results. It buys nothing: the build cache is keyed by a hash of all inputs, so anything that changed already causes a rebuild. Drop it and persist the cache.

open as a page

Why can `go doc ./pkg` and a package's pkg.go.dev page show different documentation?

level: seniorimportance: should knowfreq 38%

basics

~20 s

go doc reads source on your machine, so it shows your working tree as it is now. pkg.go.dev renders a published module version fetched through the public proxy, so it lags your code and never shows a private module.

open as a page

Why does go vet's copylocks check flag a value receiver on a struct holding a sync.Mutex?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Because the call copies the struct, and the copy carries its own mutex. Every caller then locks a lock nobody else can see, so mutual exclusion is silently gone. Nothing panics, nothing fails to compile, and the tests still pass.

open as a page

How would you make CI prove that committed generated Go files match `go generate ./...` output?

level: seniorimportance: should knowfreq 42%

basics

~20 s

On a clean checkout, run go generate over the module, stage everything, then run git diff with its exit-code flag. A non-empty diff means the committed generated files are stale, and the printed diff is the fix.

open as a page

How does the GOFLAGS environment variable change what `go build` and `go test` do?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

GOFLAGS holds a space-separated list of flags the go command applies by default to any subcommand that knows them, so GOFLAGS=-trimpath makes every build trimmed without editing a single command line. Flags given explicitly on the command line override it.

open as a page

A Go doc comment renders as one run-on paragraph — which doc comment syntax rules were missed?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

Go doc comments use a tiny syntax: an empty comment line separates paragraphs, indented lines become a code block or list, hash-space makes a heading, brackets around a name make a link. Other markdown is literal.

open as a page

Why can go test fail with a vet diagnostic before any of your tests run?

level: middleimportance: nice to knowfreq 35%

basics

~20 s

go test runs a curated subset of go vet over the package before building anything. A diagnostic there aborts the run, so you see a vet message and no PASS or FAIL lines. The -vet=off flag skips the step.

open as a page

A Go binary's //go:embed templates tree is missing _partials/header.html at runtime. Why, and how do you confirm what was embedded?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

When a //go:embed pattern names a directory, files and directories whose names begin with a dot or an underscore are skipped from the whole subtree, with no warning. Use the all: prefix on the pattern, and walk the embed.FS with fs.WalkDir to see what is really inside.

open as a page

You own a Go repo's merge queue: which of gofmt, go vet and a generate-diff check may block a merge?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Block on checks that are deterministic, fast, and fixable by the author in minutes: formatting, then vet. Keep the regenerate-and-diff check advisory until its version pinning is proven. Every blocking check must run locally with one command.

open as a page