What does `go mod tidy` change in a module's go.mod, and when do you run it?
answer
- the require block should follow the code
- adds what you import, drops what you do not
- it also maintains the indirect markers
- not an upgrade command
- CI can fail on a non-empty diff
basics
~20 sgo mod tidy makes the require block match the code: it adds a requirement for every module your packages import, drops requirements nothing imports any more, maintains the indirect markers, and syncs go.sum. Run it after changing imports.
solid answer
~40 s`go mod tidy` scans every package in the main module and their tests, works out which modules supply the imports, and rewrites go.mod so the `require` block matches exactly that set. It adds requirements that are missing, deletes requirements no package needs, and adds or removes the `// indirect` comment on each line. It also updates go.sum, adding the hashes it now needs and dropping ones it does not. It is not an upgrade command: versions already selected stay where they are, and tidy only has to choose a version when it must add a module that was not required at all. You run it whenever imports change and before opening a pull request; many teams have CI run it and fail the build if go.mod or go.sum then differ from what was committed.
code
mod · 12 linesmodule example.com/billing
go 1.27
require (
example.com/logging v1.2.0
example.com/text v0.9.0
)
require (
example.com/httpx v1.4.0 // indirect
)go deeper
Be ready to say in one sentence that tidy makes the require list match the imports in the code, and that you run it before committing whenever you add or remove an import.
Explain the mechanics: which packages tidy inspects (all build tags, all platforms, tests), that it maintains the indirect markers, and that it selects a version only for a module it has to add.
Show the operating discipline: a CI job that runs tidy and fails on a diff, go.mod and go.sum committed with the code, and go mod why -m as the tool you reach for when a tidy result surprises you.
Own the policy angle: who is allowed to add a direct dependency, how the tidiness check interacts with a pinned toolchain so everyone tidies identically, and what a growing require block costs the team over time.
## What go.mod holds A Go module is a tree of packages with a `go.mod` file at its root. That file declares the **module path** on its `module` line (the import-path prefix for every package inside the module), a `go` line, sometimes a `toolchain` line, and one or more `require` blocks. Each `require` line names another module and the **minimum version** of it this module needs. Nothing in the compiler keeps that list honest. You can delete an import from a `.go` file and the stale `require` line stays; you can add an import and the file has no requirement for it. `go mod tidy` is the command that reconciles the two. ## What tidy actually does It loads the packages of the main module, resolves every import, and then: - **Adds** a `require` line for any module that provides a package you import but that is not required yet. If it has to invent a version, it takes the latest release of that module. - **Removes** any `require` line for a module that no longer provides a package needed to build any package or test in the main module. - **Maintains `// indirect`.** A require gets that comment when no package in your own module imports anything from it; the comment is removed when your code starts importing it directly. - **Syncs `go.sum`**, adding the hashes needed for the resulting build and deleting entries nothing refers to any more. "Every package" is broader than what a single `go build` sees. Tidy considers the packages of the main module for **all build tags and all target platforms**, and it considers the **test** imports of those packages. Since Go 1.24 it also keeps the modules named by `tool` directives in go.mod, which is how executable dependencies are recorded. That breadth is why tidy sometimes keeps a module you cannot find an import for on your own machine — the import is behind a build constraint for another GOOS, or it is in a `_test.go` file. ## What tidy does not do - It **does not upgrade**. If go.mod already requires a module at some version, tidy leaves that version alone; getting newer versions is a separate, deliberate action. - It **does not** rewrite `replace` or `exclude` lines you wrote by hand. - It **does not** tell you *why* something is kept. When a removal or a retention surprises you, `go mod why -m <module>` prints the chain from a package in your module to that module, and `(main module does not need module …)` when nothing reaches it. ## Reading a tidy diff Because tidy is deterministic for a given code base and Go version, its diff is meaningful in review: - a line **added without `// indirect`** means your code now imports something new — a real dependency your team has taken on; - a line **losing** `// indirect` means code that used to reach a module transitively now imports it directly; - a line **removed** means nothing in the module reaches it any more, which should normally line up with an import disappearing in the same pull request. In Go 1.27, `go mod tidy` writes modules whose `go` line is 1.27 or later using a **two-block require layout**: direct requirements in one `require` block, indirect ones in a second. Older layouts stay valid; the split just makes the direct/indirect distinction obvious at a glance. ## In practice The habit that avoids trouble is: change imports, run `go mod tidy`, run the tests, commit go.mod and go.sum **in the same commit as the code**. The CI check is the enforcement — a job that runs `go mod tidy` with the repository's Go version and fails if the working tree changed. That turns "did someone forget to tidy?" from a review judgment into a build result, and it keeps the require block a trustworthy statement of what the module depends on.
- Does `go mod tidy` upgrade the dependencies it keeps?No. Versions already recorded in go.mod stay exactly as they are. Tidy only picks a version when it must add a module that was not required at all, in which case it takes that module's latest release. Moving an existing requirement to a newer version is a separate, explicit action.
- How does `go mod download` differ from `go mod tidy`?`go mod download` fetches modules into the local module cache so a later build does not need the network; it does not decide what your module requires. `go mod tidy` edits go.mod and go.sum so the requirements match your imports. Downloading never changes which versions are selected.
- Why does tidy sometimes keep a module you cannot find any import for?Tidy considers the main module's packages under all build tags and target platforms, plus their test imports, plus modules named by `tool` directives. The import may live in a `_test.go` file or behind a constraint for another GOOS. `go mod why -m <module>` prints the path that keeps it.
saying these in an interview costs you the question
- Says tidy upgrades dependencies to their newest versions
- Thinks tidy edits go.sum only, never go.mod
- Claims tidy ignores test files and build-tagged code
- Believes tidy deletes every require marked // indirect
- Commits code changes without the matching go.mod change