skip to content

What does `go get -u ./...` do in a Go module, and how does `-u=patch` differ?

level: juniorimportance: must knowfreq 58%

answer

  1. one flag widens, one flag narrows
  2. the pattern names your packages, not modules
  3. newest minor or patch, versus patch only
  4. v1.4.0 to v1.4.7, never v1.5.0
  5. it edits go.mod; it installs nothing

basics

~20 s

go 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
text
$ 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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