In the go command, what do -mod=mod, -mod=readonly and -mod=vendor do, and which applies by default?
answer
- three answers to one question about sourcing
- one of them may rewrite a tracked file
- one refuses to rewrite and fails instead
- one never looks outside the repository
- the refusing one has been the default for years
basics
~20 s-mod=mod lets the go command update go.mod as it resolves imports, -mod=readonly makes it fail instead of editing, and -mod=vendor resolves every import from the vendor directory. readonly is the default, or vendor once a vendor directory exists.
solid answer
~40 s`-mod` answers two questions at once: where packages come from, and whether the build may rewrite `go.mod`. `-mod=mod` is the historical behaviour — an import with no matching requirement is added to `go.mod` during an ordinary `go build`. `-mod=readonly` has been the default since Go 1.16 and forbids that: if the build needs a requirement the file does not have, it fails and names the `go get` command to run, so a build can never silently change your dependency list. `-mod=vendor` ignores the module cache and the network entirely and resolves every import from `vendor/`; it is chosen automatically when a `vendor` directory is present and `go.mod` declares `go 1.14` or later. Passing `-mod=mod` or `-mod=readonly` explicitly is how you build a repository that has a vendor directory without using it.
go deeper
Recall that there are three values and what each one is for. The one you will meet first is readonly, because it is the default and it is what produces the error telling you to run a go get command.
An interviewer expects the mechanics: that readonly forbids the build from editing go.mod, that mod permits it, that vendor changes the source of packages entirely, and how the default is derived rather than configured.
Show that you use the override diagnostically — building with -mod=readonly to prove a failure comes from stale vendored source — and that you would pin the mode in CI rather than let it depend on whether a directory is present.
The tradeoff to own is implicitness. A mode inferred from a directory's existence is convenient for newcomers and invisible in review; decide whether your build configuration should state it outright.
## One flag, two decisions The `-mod` flag is accepted by the build-ish `go` subcommands — `go build`, `go test`, `go run`, `go list`, `go vet` and friends. It controls **where imports are resolved from** and **whether `go.mod` may be modified as a side effect of building**. There are three values. ### `-mod=mod` The original behaviour of modules. The build resolves imports against the module graph, and if it encounters an import that no requirement in `go.mod` provides, it works out which module provides it, adds a `require` line, and carries on. `go.mod` (and the recorded hashes) are updated as a side effect of an ordinary build. This is convenient and was the source of a real class of problem: a build could change your dependency set. Two engineers running `go build` on the same commit could end up with different `go.mod` files, and a stray import in a scratch file could quietly add a dependency nobody reviewed. ### `-mod=readonly` The default since Go 1.16. The `go` command may **read** `go.mod` but not write it. If the build needs something the file does not already provide, the command stops with an error that names the exact `go get` invocation that would fix it. Dependency changes become an explicit act, performed by a command whose whole job is to change dependencies, rather than a side effect of compiling. This is why, on a modern Go toolchain, `go build` never surprises you by editing a tracked file — and why a missing requirement shows up as a build failure with a suggested command rather than as a diff. ### `-mod=vendor` A different source of truth entirely. Imports resolve from the `vendor/` directory at the module root; the module cache and the network are not consulted. An import with no package under `vendor/` fails immediately rather than triggering a lookup, because import lookup is disabled in this mode. `go.mod` is not modified. Before building, the `go` command cross-checks `vendor/modules.txt` against `go.mod` and refuses to start if they disagree, so vendor mode cannot silently build against a dependency set that does not match the module requirements. ## How the default is chosen The default is `readonly` — **unless** the main module has a `vendor` directory and its `go.mod` declares `go 1.14` or later, in which case the default is `vendor`. Nobody configures this; it follows from the directory existing. That is a deliberate design choice (vendoring should just work for anyone who clones the repository) with an important consequence: adding `vendor/` to a repository changes how everyone's builds resolve packages, whether or not they noticed the directory. ## Overriding Because the vendor default is inferred rather than declared, you sometimes need to override it: ``` go build -mod=readonly ./... ``` This builds the same code against the module cache while leaving `vendor/` untouched on disk. It is the standard way to answer "is this failure caused by my code, or by a stale vendored tree?" — if the build succeeds with `-mod=readonly` and fails without it, the vendored source is the problem. The reverse case matters too. If you want vendor mode to be an explicit, checked property of your build rather than a consequence of whether the directory happens to be checked out, set it in the environment: ``` GOFLAGS=-mod=vendor ``` `GOFLAGS` applies its contents to every `go` command that accepts them. Setting it in CI makes the intent visible in the build configuration, and turns "someone deleted vendor/" into a loud failure rather than a silent switch back to the module cache. ## What each value does not do - `-mod=readonly` does not make the module cache read-only, and does not stop downloads; it only forbids editing `go.mod`. - `-mod=vendor` does not fetch anything, ever, and does not regenerate the vendor directory. Only `go mod vendor` writes that tree. - `-mod=mod` does not delete or bypass `vendor/`; it simply ignores it for that command, leaving the directory in place. ## Choosing in practice Most repositories want the default. Vendored repositories want the vendor default, ideally made explicit in CI. `-mod=mod` is best treated as a deliberate, occasional act rather than a habit — if you find yourself reaching for it to make a build pass, the honest fix is usually a `go get` that updates requirements on purpose.
- Why did Go make `-mod=readonly` the default?So a build cannot silently change your dependency graph. Under `-mod=mod`, a stray import added `require` lines during an ordinary `go build`, which let CI and a developer's machine drift apart on the same commit. Under readonly the build fails and names the `go get` command to run, making every dependency change deliberate and reviewable.
- Your repository has a vendor directory but you want one build against the module cache. How?Pass `-mod=readonly` or `-mod=mod` explicitly, for example `go build -mod=readonly ./...`. That overrides the automatic vendor selection for that command while leaving `vendor/` on disk untouched. It is the usual way to tell whether a failure comes from stale vendored source or from the code itself.
- Can you fix the mode without typing the flag on every command?Yes — set `GOFLAGS=-mod=vendor` (or `-mod=readonly`) in the environment and it applies to every `go` command that accepts the flag. Teams often do this in CI so the mode is a stated property of the build rather than something inferred from whether a directory happens to be present in the checkout.
saying these in an interview costs you the question
- Thinks -mod=mod is still the default
- Says -mod=vendor may download missing packages
- Believes -mod=vendor must be passed for vendor/ to be used
- Claims -mod=readonly makes the module cache read-only
- Thinks the mode is declared inside go.mod