In a go.mod require line, what does the `// indirect` comment mean?
answer
- who imports it decides the marker
- not your code, but your dependency's
- the go command writes the comment, not you
- bookkeeping only; the build reads the version
- losing the comment means a new direct import
basics
~20 sIt marks a require line for a module that no package in your own module imports directly. The module is listed because something you depend on needs it, or because your module has to record a minimum version for it. The go command writes and removes the comment itself.
solid answer
~50 sA requirement is *direct* when some package in the main module, or one of its tests, imports a package from that module; otherwise `go mod tidy` writes `// indirect` on the line. Indirect entries appear for two reasons: a dependency of yours needs the module, and your go.mod records a minimum version for every module that supplies a transitively imported package so the whole dependency graph does not have to be loaded to build you. The comment is pure bookkeeping — the build reads the module path and version on the line, not the comment — and you should never edit it by hand. In review it is the signal that matters: a line losing `// indirect` means your code now imports that module directly, which is a new dependency your team owns rather than inherits.
code
mod · 8 linesrequire (
example.com/logging v1.2.0
example.com/text v0.9.0
)
require (
example.com/httpx v1.4.0 // indirect
)go deeper
Recall the one-line rule: no package of yours imports that module, so the go command marked the line indirect. Know that you never type or delete the comment yourself.
Explain both sources of indirect entries — a dependency needs it, and your go.mod records minimums for transitively imported modules — and that the comment carries no meaning for the build itself.
Use the distinction in review: treat a new direct requirement as new surface area your team owns, and be able to trace any line back to the code that keeps it with go mod why -m.
Frame it as policy: direct requirements are the dependency budget you can actually govern, so decide who approves adding one and how the indirect tail is kept from silently becoming everyone's problem.
## Direct versus indirect Every `require` line in a `go.mod` names a module and a minimum version of it. The `// indirect` comment on some of those lines answers one narrow question: **does any package in this module import a package from that module?** - If yes, the requirement is **direct** and carries no comment. - If no, the requirement is **indirect** and the go command writes `// indirect` after the version. "Any package in this module" includes the module's tests, and it is evaluated across build tags and target platforms, so a module imported only from a `_test.go` file or only on one GOOS still counts as direct. ## Why an indirect requirement exists at all There are two everyday reasons a module you never import appears in your own `require` block. **Something you depend on needs it.** Your module requires module A; A's own go.mod requires B. B has to be in the build, and its version is part of your build's identity, so it is recorded. **Your go.mod records the modules that provide transitively imported packages.** Modern modules keep enough requirements written down that the go command can resolve the build without opening the go.mod of every module in the world beneath you — the graph is pruned. The cost of that is a longer indirect list; the benefit is that builds load a much smaller graph and are reproducible from what your own file says. There is a third, less common case: an indirect line can record a **minimum** for a module higher than any of your dependencies asks for. The go command keeps such a line because deleting it would change what gets built. ## The comment has no semantics This is the point candidates most often get wrong. `// indirect` is a comment. It is written and maintained by the go command, it is not read by the compiler, and version selection uses the module path and version on the line whether or not the comment is there. Deleting the comment by hand does not make a dependency direct; deleting the whole line does not make the module disappear from the build, because it will simply be re-derived (and, if it is still needed, put back by the next `go mod tidy`). ## What makes a line flip - **Indirect to direct:** you add an import of a package from that module. The next `go mod tidy` drops the comment. The version does not change. - **Direct to indirect:** you delete the last import of that module, but another dependency still needs it. Tidy keeps the line and adds the comment rather than deleting the requirement. - **Direct to gone:** you delete the last import and nothing else needs it either. Tidy removes the line. ## Why reviewers care The direct/indirect split is the closest thing go.mod has to a statement of intent. Direct requirements are the dependencies your team chose and has to maintain, upgrade and justify; indirect ones came with those choices. A pull request that adds a direct requirement is adding surface area — new code compiled into your binary, a new licence, a new upgrade obligation — while one that only shuffles indirect lines is usually the toolchain doing bookkeeping. In Go 1.27, `go mod tidy` makes this even easier to read: for modules on a `go 1.27` or later line it writes the requirements as **two `require` blocks**, direct in one and indirect in the other, so the two categories are separated in the file rather than interleaved with comments. ## Common misreadings - *"Indirect means unused, so I can delete it."* No — indirect means *you* do not import it, not that the build does not need it. - *"Indirect dependencies are not compiled in."* They very much can be: a package from an indirect module is compiled into your binary whenever a dependency you do import uses it. - *"I'll mark it direct so it stops moving."* The comment is regenerated on the next tidy; the way to change it is to change what your code imports. When you want to know why any particular line is there — direct or indirect — `go mod why -m <module>` prints the chain of packages from your module to that one, and says `(main module does not need module …)` when nothing reaches it.
- What happens to the comment when your code starts importing that module directly?The next `go mod tidy` — or a `go get` of that package — removes `// indirect` from the line and leaves the version untouched. Nothing else about the build changes; the file now simply records that the module is one you import yourself rather than one you inherited.
- Why does removing an import sometimes convert a direct require into an indirect one instead of deleting it?Because another module in the build still needs it. Tidy deletes a requirement only when nothing reaches the module at all; when your own import goes away but a dependency still uses it, the line stays and gains the `// indirect` comment.
- Does the `// indirect` comment affect which version of the module is selected?No. Selection reads the module path and version on the require line; the comment is documentation the go command maintains. An indirect line still states a minimum version that participates in resolving the build exactly like a direct one.
saying these in an interview costs you the question
- Thinks // indirect means unused and safe to delete
- Edits or adds the // indirect comments by hand
- Says indirect modules are not compiled into the binary
- Believes the comment changes version selection
- Cannot say what makes a requirement direct