What does `go get -u ./...` do in a Go module, and how does `-u=patch` differ?
answer
- one flag widens, one flag narrows
- the pattern names your packages, not modules
- newest minor or patch, versus patch only
- v1.4.0 to v1.4.7, never v1.5.0
- it edits go.mod; it installs nothing
basics
~20 sgo get -u ./... upgrades every module behind the packages your module imports, directly or transitively, to its newest minor or patch release. go get -u=patch caps each move at the newest patch of the minor already selected.
solid answer
~40 s`./...` names every package in the main module, and `-u` tells the go command to upgrade the modules that provide those packages and everything they import, in one pass, to the newest available minor **or** patch release. `-u=patch` does the same walk but caps each move at the latest patch of the minor version currently selected, so `v1.4.0` can become `v1.4.7` but never `v1.5.0`. Both rewrite the `require` lines in `go.mod` and add the new hashes to `go.sum`; neither builds or installs anything, because in modern Go `go get` only manages dependencies and `go install pkg@version` is what installs binaries. Neither one crosses a major version either: `v2` of a module is a different module path, so `-u` will never move you from `v1` to `v2`.
code
text · 5 lines$ go list -m -u all
example.com/service
example.com/api v1.6.0 [v1.7.1]
example.com/log v1.2.0 [v1.2.4]
example.com/lib v1.4.0 [v1.4.2]go deeper
Be ready to say what each piece means: ./... is your own packages, -u upgrades the modules behind them, -u=patch keeps you on the same minor. Know that go.mod and go.sum are what change.
Explain the transitive reach of -u and why the default -mod=readonly makes upgrades an explicit, reviewable step rather than something a build does for you.
Show judgment about blast radius: when a patch sweep is enough, when you take the minor bump, and what you check in tests and in the go.mod diff before merging either.
Own the cadence. Argue for whether a fleet runs patch-only on a schedule with minor bumps batched deliberately, and what evidence would make you change that policy.
## What the command is actually made of `go get -u ./...` has three separable parts, and most confusion about it comes from blurring them. **`go get`** is the dependency-management command. In current Go it does exactly one job: it changes which module versions your main module requires, editing `go.mod` and recording the new hashes in `go.sum`. It stopped building and installing packages in Go 1.18; installing a program from a module you do not depend on is `go install example.com/cmd@latest`, which does not touch your `go.mod` at all. If you remember `go get` as "the command that downloads and installs a tool", that memory is from a Go that no longer exists. **`./...`** is a package pattern, not a module pattern. It expands to every package in the directory tree rooted at the current directory — in practice, all the packages of your own module. You are naming *your* packages, and the upgrade is derived from what they import. **`-u`** is the upgrade flag. It says: for the packages named on the command line, upgrade the modules providing them *and the modules providing everything they import, directly or indirectly*, to newer minor or patch releases when such releases exist. That transitive reach is the part people underestimate. `go get -u ./...` is not "bump my direct dependencies"; it is "bump essentially the whole build list", and a single run can move dozens of modules across minor versions. ## `-u` versus `-u=patch` `-u=patch` performs the same traversal but restricts every module to the highest patch release within the minor version it is already on. A module at `v1.4.0` may reach `v1.4.7`; it will not reach `v1.5.0`. Semantically a patch release should contain only bug fixes, so `-u=patch` is the low-blast-radius upgrade: it is what a scheduled bump job runs when it wants the diff to be safe to merge with a light review. `-u` is the higher-yield, higher-risk upgrade: new minors bring new behaviour, new deprecations, and sometimes new transitive requirements of their own. Both flags obey the same two limits. First, they only consider **release** versions — a module with only a pre-release tag such as `v1.5.0-rc.1` is not chosen by `-u`. Second, they never change a module path, and a major version from `v2` onward *is* a different path (`example.com/lib/v2`). So `-u` cannot perform a major upgrade; that is an import-path edit you make deliberately. ## Looking before you leap The read-only companion is `go list -m -u all`. `-m` makes the command list **modules** rather than packages, `all` is the module pattern covering everything in the build list, and `-u` annotates each line with the newest available version in square brackets: ``` example.com/api v1.6.0 [v1.7.1] example.com/log v1.2.0 [v1.2.4] ``` A line with no bracket is already current. This is the survey you run before deciding whether this week's upgrade is a patch sweep or a real minor bump, and it changes nothing on disk. ## Why the upgrade has to be explicit at all Since Go 1.16 the build commands default to `-mod=readonly`: `go build` and `go test` will not silently add or upgrade a requirement to make your code compile — they fail and tell you which `go get` to run. That is a deliberate design choice. Upgrades are a version-control event, reviewable as a diff of `go.mod` and `go.sum`, rather than something that happens invisibly on a developer's machine and produces a build nobody else can reproduce. ## After the upgrade Run the tests. Then run `go mod tidy`, which adds any requirement that the new versions made necessary and drops any that became unused; note that tidy will not undo your upgrade — it does not downgrade requirements that are still needed. Review the `go.mod` diff line by line: on a modern `go` directive line, an upgrade of one direct dependency frequently rewrites several `// indirect` lines too, and those are the lines where behaviour you never asked about can change.
- Which files change when you run `go get -u ./...`, and which do not?`go.mod` and `go.sum` change: require lines move to new versions and `go.sum` gains hashes for the new module versions (old entries may linger until `go mod tidy`). No source file changes, no binary is produced, and no `vendor` directory is refreshed — if you vendor, you re-run `go mod vendor` yourself afterwards.
- How do you see what upgrades are available without changing anything?`go list -m -u all`. The `-m` flag lists modules instead of packages, `all` covers the whole build list, and `-u` appends the newest available version in square brackets after the current one. Lines with no bracket are already up to date. It is purely read-only, which makes it the right thing to run in CI or in a report.
- Why will `go get -u` never move a dependency from v1 to v2?From `v2` onward the major version is part of the module path — `example.com/lib/v2` is a different module from `example.com/lib`, and both can be in one build. `-u` upgrades a module within its own path, so it cannot cross that boundary. A major upgrade means editing import paths in your code, which is a deliberate change, not a flag.
saying these in an interview costs you the question
- Thinks go get still builds and installs binaries
- Believes -u only touches direct dependencies
- Expects -u to jump from v1 to v2 automatically
- Says -u=patch upgrades to the newest minor release
- Thinks go build silently upgrades go.mod when needed