JavaRush /Courses /Go SELF /go mod tidy and go get — version management

go mod tidy and go get — version management

Go SELF
Level 35, Lesson 1
Available

1. Why are there two commands at all: go get and go mod tidy

When a project is small, it can feel like dependencies “just appear somehow”: you import a package and everything builds. But as soon as you start using external libraries (not from the standard library), the project gains one more important task: pin exactly which versions of external code you use, so the build is reproducible. And this is where Go behaves like an adult: it does not just “download something from the internet”, it carefully records your choice in the project files.

In this lecture, we will cover two commands that most often inspire either respect or mild panic (usually both at the same time): go get and go mod tidy. Let’s agree up front: we are not going deep into replace, vendor, and other “Swiss army knives” of the module system. Our goal is to learn how to manage dependency versions and keep order in go.mod/go.sum.

Mental model: what is considered a “dependency”

In plain terms, a dependency appears when your code imports a package that is not part of the standard library. The standard library is fmt, strings, time, net/http, and friends. Anything that looks like github.com/... or golang.org/x/... is external.

Then the important part begins. Go thinks in modules, not “packages”. One module can contain many packages. You import a package, but you pin (in go.mod) the module and its version. That’s why sometimes you import golang.org/x/net/html, but the module golang.org/x/net as a whole is pinned in go.mod.

And one more key point: dependencies can be direct (direct) and transitive (transitive). Direct ones are what you import. Transitive ones are what your dependencies import. It’s like meeting people through friends: you invited one person, and they showed up with friends (sometimes helpful, sometimes noisy—but that’s life).

2. What go get does: “choose a version and record the choice”

go get is a command about selecting module versions. Not about “downloading a library”, not about “installing it on your computer”, but specifically about telling the project: “for this dependency, use this version”. After that, Go updates go.mod and (almost always) go.sum.

There is a useful detail: modern go commands often do not try to fix the problem automatically, and instead show an error and plainly recommend the command you should run. For example, during a build you might see a message like: “no required module provides package ...; to add it: go get ...”. This is a normal scenario: Go does not “silently do magic”; it tells you what is wrong and what the next expected step is.

Basic scenario: “add a dependency based on an import”

Imagine that in our training application todoapp we want to add a small feature: normalize task titles not only with strings.TrimSpace, but also, say, prepare for more complex text processing. As an example, let’s take the external package golang.org/x/net/html (it often appears in real projects and works well as a “training” dependency).

Suppose you added the import in code and then run a build:

go build ./...

If the module is not added, Go may show an error and a hint “to add it: go get ...”. This style of messaging is characteristic of module-aware mode and is intentionally designed so you do not have to guess which command to type.

After that you run:

go get golang.org/x/net/html

And now Go does the important things: it selects a version of the golang.org/x/net module, writes it into go.mod, and adds checksums to go.sum.

Version management: @version, @latest, rollback

It is very useful to think of go get as a “version switch”.

You can specify a version explicitly:

go get golang.org/x/net@v0.20.0

You can ask for “the freshest one” that fits the version selection rules:

go get golang.org/x/net@latest

And yes, you can roll back to an older one:

go get golang.org/x/net@v0.19.0

It is important to internalize this idea: go get is not “install a library”, it is “change the project’s version requirements”. That’s why it almost always produces a diff in go.mod/go.sum. And that’s normal.

4. What go mod tidy does: “clean up and synchronize facts”

If go get is about “choosing versions”, then go mod tidy is about “let’s bring the dependency declaration in line with reality”.

Reality has two sides: your code and your tests. go mod tidy looks at imports in the sources and in _test.go files, and does two main things: it adds what is missing for builds/tests, and it removes what is no longer used. At the same time, both go.mod and go.sum are updated.

Imagine go.mod as a shopping list, and the project as your kitchen. go mod tidy is when you not only bought milk (go get), but also removed “mango” from the shopping list—the thing you added in a burst of enthusiasm two weeks ago and never bought even once.

5. Useful nuances

Why // indirect appears in go.mod

Beginners often perceive // indirect as something like “a second-class dependency”. In reality, it is just a hint: the dependency is not imported directly by your code, but it is required for the build because of other modules.

go mod tidy is exactly the tool that likes to add these markers and remove them when the situation changes. An important psychological point: do not fight // indirect manually, do not remove them “for aesthetics”, and do not add them “to make it correct”. The tool determines correctness, and your job is to understand the meaning and keep the project buildable.

Mini-table: who is responsible for what

After the theory, it helps to reinforce “who is in charge” in which situations. The table is small, but it often saves 20 minutes of irritation.

Situation in the project What you want to do The command that is usually appropriate What will change
You added a new import of an external package; the build complains Pin the module and version
go get <module-or-package>
go.mod, go.sum
You want to update a dependency to a specific version Select a version
go get <module>@<version>
go.mod, go.sum
You removed an import/code, but dependencies are still left as “tails” Remove the extra
go mod tidy
go.mod, go.sum
You suspect go.mod and the code got “out of sync” Synchronize facts
go mod tidy
go.mod, go.sum

A phrase worth remembering: go get is about versions, go mod tidy is about order.

6. Example with todoapp

Let’s imagine the project structure (simplified):

todoapp/
  go.mod
  go.sum
  cmd/todoapp/main.go
  internal/todo/normalize.go

Add a normalization function without external dependencies

Let’s start with something simple and familiar. File internal/todo/normalize.go:

package todo

import "strings"

// NormalizeTitle converts the title to a neat form.
func NormalizeTitle(s string) string {
	return strings.TrimSpace(s)
}

And now we will use this in cmd/todoapp/main.go:

package main

import (
	"fmt"

	"example.com/todoapp/internal/todo"
)

func main() {
	fmt.Println(todo.NormalizeTitle("   buy milk   ")) // buy milk
}

As long as everything stays within the standard library, dependencies do not change.

Add an external import to demonstrate the mechanics

Now let’s add an “artificial” (but plausible) reason to use an external package. For example, we want to parse some HTML in the future (even if this is just a learning example). Let’s change normalize.go by adding a reference to a type from golang.org/x/net/html:

package todo

import (
	"strings"

	"golang.org/x/net/html"
)

// NormalizeTitle converts the title to a neat form.
func NormalizeTitle(s string) string {
	_ = html.Node{} // just so the import is "alive"
	return strings.TrimSpace(s)
}

Most likely, after that go build ./... will start complaining that the required module is not added, and it will suggest running go get. This hint format is expected behavior for Go tooling.

Fix — run:

go get golang.org/x/net/html

After that, open go.mod: you will see require golang.org/x/net .... Entries in go.sum will appear (or be updated).

Remove the external import and clean up the tails

Suppose you changed your mind, deleted html.Node{} and the import itself. After that, the project builds again, but the dependency may remain in go.mod because you added it once before.

This is where go mod tidy time comes:

go mod tidy

This command synchronizes dependencies with the actual imports. It will remove from go.mod what is no longer needed and tidy up go.sum. This is expected: both commands manage these files as intended.

7. How to read go.mod and not be afraid of go.sum

When you work in a team or simply value neatness, you will see diffs in go.mod and go.sum. This is a normal part of life in a Go project.

go.mod is readable and relatively “human-friendly”. You can see module, go, and the require list. You can read it and sometimes even edit it manually (but carefully, understanding the consequences).

go.sum is not “a second go.mod”. It stores module checksums and is used to verify integrity and reproducibility. It is better to treat it as an output produced by tooling. In real practice, the correct workflow is almost always this: you change code and versions using commands, and go.sum updates automatically. Editing go.sum by hand usually ends with either breaking the build, or rerunning go mod tidy a minute later, and the tool putting everything back.

8. Dependency lifecycle diagram

Sometimes it helps to see the process as a “pipeline” rather than a set of random commands.

flowchart TD
    A[Added an import of an external package in code] --> B[go build ./... or go test ./...]
    B -->|error: no module| C[go get 
 
  ]
    C --> D[go.mod/go.sum updated]
    D --> E[code builds again]
    E --> F[removed import/code]
    F --> G[go mod tidy]
    G --> H[go.mod/go.sum synchronized]

 

The rhythm itself matters here: go get usually appears when you add dependencies or change versions, and go mod tidy appears when you want the dependency files to match the current state of the code again.

9. Common mistakes

Error #1: treating go get as “just download the library once”.
This mindset comes from worlds where you install packages into the system manually. In Go, it works differently: go get pins dependency versions for the current module and changes go.mod/go.sum. So “I just wanted to download it” turns into a diff in the repository—and that is not a bug, it is the point of the command.

Error #2: running go mod tidy without realizing it also accounts for tests.
Sometimes a student removes an import from the main code, runs go mod tidy, and the dependency somehow remains. A common reason is that the import exists in _test.go. This is not “Go being stubborn”; you just forgot that tests are also part of the project, and they need dependencies to compile.

Error #3: editing go.sum by hand to “make it match”.
go.sum is not a place for manual creativity. If you see it changed, it is usually the result of dependency changes or version changes. The right action is to understand the cause (what you added, updated, or removed), not to “tweak the file”, because the tool will verify integrity anyway and may regenerate entries again.

Error #4: confusing a package import path and a module path when running go get.
Sometimes people run go get with one path, but import another, and then it feels like “magic”. The rule is simple: you can point go get either to a specific package (.../html) or to the module root (.../net). Go will figure out which module is needed. But the import in your code must be correct and match the real package location.

Error #5: being scared of changes in go.mod after go get or go mod tidy.
Go tools predictably update dependency files because their main job is to manage project dependencies, and both go get and go mod tidy do exactly that. If you try to “roll back because it’s scary” every time, you remove the project’s main advantage: reproducibility.

1
Task
Go SELF, level 35, lesson 1
Locked
Missing import
Missing import
1
Task
Go SELF, level 35, lesson 1
Locked
Pin a quote
Pin a quote
1
Task
Go SELF, level 35, lesson 1
Locked
Remove leftover dependencies
Remove leftover dependencies
1
Task
Go SELF, level 35, lesson 1
Locked
Test dependencies
Test dependencies
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION