What does an `exclude` directive in go.mod do when something in the module graph requires that exact version?
answer
- the version is treated as never published
- resolution moves up, never down
- only your own go.mod gets a vote
- nothing higher means a hard failure
- a raised require floor usually says it better
basics
~20 sAn exclude line in the main module's go.mod makes the go command act as if that version of that module does not exist. Requirements on it move up to the next higher available version, and the build fails if none exists.
solid answer
~50 s`exclude example.com/lib v1.4.2` removes that one version from the candidate set while the go command resolves the module graph. Any requirement on the excluded version is treated as a requirement on the next higher available version instead — resolution only ever moves upward, never down to v1.4.1. If no higher version is published, the build fails outright rather than falling back. Like `replace`, `exclude` is honoured only in the main module's `go.mod`, so it cannot protect anyone who imports you. It also has no effect on the module proxy: the excluded version stays published and downloadable, it is simply not selectable in this build. In practice most teams raise the `require` floor instead, because a floor is a single positive statement that tooling understands, while an exclude has to be added again for each new bad release.
code
mod · 9 linesmodule example.com/internal/billing
go 1.25
require example.com/shared/auth v0.6.0
// v0.7.1 panics on empty tokens. Anything requiring it resolves to v0.7.2
// instead; if no higher version existed, the build would fail here.
exclude example.com/shared/auth v0.7.1go deeper
Know that exclude names one module version in your own go.mod and takes it out of consideration for this build only — it does not unpublish or delete anything.
Be able to say which way resolution moves: a requirement on an excluded version is raised to the next higher one, and the build fails when there is no higher one to raise it to.
Argue the tool choice. Show when a raised require floor expresses the intent better, and how a stale exclude nobody tidies becomes a resolution constraint years later.
Decide whether exclude belongs in your organisation's playbook at all, given that it never reaches consumers, and what mechanism you use instead to keep a known-bad release out of every service.
## The mechanic When the go command resolves dependencies it walks the module graph, collecting the versions each module says it needs, and picks a version for every module in the build. An `exclude` directive edits the candidate set before that decision: `exclude example.com/lib v1.4.2` says "pretend this version was never published." The interesting part is what happens to a requirement that names the excluded version. It is not dropped, and it does not fall back. It is **raised to the next higher available version** of that module. If v1.4.2 is excluded and the module has published v1.4.3, the requirement becomes v1.4.3. If v1.4.2 is the newest release, there is nothing higher to move to and the go command reports an error rather than quietly using something older. Resolution moving only upward matters, because the intuition many people bring — "exclude the bad one and you fall back to the previous good one" — is exactly backwards. Excluding a version can only ever *raise* what gets built. ## Where it may live `exclude` shares the main-module-only rule with `replace`. Only the `go.mod` in the module you invoke the go command from supplies exclusions. Publishing a library with an `exclude` line does not protect its consumers; their resolution never reads it. This is the single most common misunderstanding of the directive, and it is why it is a poor tool for saying "nobody should use v1.4.2 of this thing." ## What it does not do - It does not remove the version from the module proxy. The artifact stays published and fetchable by anyone. - It does not remove the module from your build. The module is still there; only that one version is off the table. - It does not carry a reason. Whatever you know about why v1.4.2 is bad lives in a `//` comment beside the line or nowhere. - It is not tidied away. `go mod tidy` will not remove an `exclude` that no longer matters, so stale exclusions accumulate and quietly constrain resolution years later. ## Why `require` is usually the better tool Suppose a transitive dependency drags in `example.com/lib v0.7.1`, which has a bug. Two options: ``` exclude example.com/lib v0.7.1 ``` versus raising the floor in your own requirements: ``` require example.com/lib v0.7.2 // was v0.7.1: panics on empty tokens ``` The `require` form states a positive minimum. It is what `go get example.com/[email protected]` writes for you, it is visible to anything that reads requirements, it composes with the rest of resolution naturally, and it keeps working when v0.7.3 and v0.7.4 appear. The `exclude` form blocks exactly one version, so a second bad release needs a second line, and a build that would otherwise have chosen a fine version can be pushed somewhere unexpected. That leaves `exclude` for the narrow case where you must block a specific version but cannot or will not raise the minimum for everything — for example when the next higher version is the one you actually want and a lower requirement elsewhere keeps dragging the build back to the bad one. ## Editing and inspecting ``` go mod edit -exclude=example.com/[email protected] go mod edit -dropexclude=example.com/[email protected] go mod edit -json # shows the Exclude block as data ``` After changing an exclusion, `go list -m all` shows the resolved build list, which is where you confirm the version actually moved and to what. ## The short version to say out loud Exclude is a per-build, main-module-only filter that deletes one version from consideration and pushes any requirement on it upward. It does not unpublish anything, does not reach consumers, and is almost always a worse expression of intent than a raised `require` floor.
- Why do most teams raise the `require` floor instead of adding an `exclude`?A require line is a positive minimum: it is what `go get module@version` writes, it keeps holding as newer releases appear, and any tool reading requirements can see it. An exclude blocks exactly one version, needs a new line for each bad release, reaches no consumer, and is never tidied away once stale.
- Does excluding a version stop it from being downloaded from a module proxy?No. Exclude is a resolution-time filter inside one module's build. The version stays published, stays fetchable, and stays selectable for anyone whose own go.mod does not exclude it. It changes what your build chooses, not what exists.
- What happens if the excluded version is the highest one published?The build fails. Requirements on an excluded version are raised to the next higher available version, and when there is nothing higher the go command reports an error rather than silently selecting a lower version. Excluding can only push resolution up.
- Will `go mod tidy` remove an exclude line that no longer affects anything?No. Tidy manages require lines; it leaves exclude and replace directives alone. Stale exclusions therefore survive indefinitely and can constrain resolution long after the reason for them is forgotten, which is an argument for a comment on every one you add.
saying these in an interview costs you the question
- Says exclude falls back to the previous lower version
- Thinks an exclude blocks the version for downstream consumers
- Confuses exclude with dropping the module from the build
- Believes go mod tidy prunes stale exclude lines
- Claims exclude removes the version from the proxy