skip to content

Dependency Resolution

A module names itself and its requirements in go.mod, and the go command resolves them into one build list — the lowest version that satisfies everyone, with each major version at its own import path.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

What does a `replace` directive in go.mod do, and how do you point one at a local directory?

level: juniorimportance: must knowfreq 60%

answer

  1. a redirect written in your own go.mod
  2. two right-hand forms, only one has a version
  3. a directory target needs its own go.mod
  4. it swaps the source, it adds no requirement

basics

~20 s

A replace directive in go.mod redirects a module path to something else: either a different module at a stated version, or a directory on disk. The build then compiles that code instead of the version the proxy would serve.

solid answer

~50 s

`replace` rewrites where the go command gets a module's source. The left-hand side is the module path being redirected, optionally with a version to restrict the redirect to that one version; leaving the version off redirects every version. The right-hand side takes one of two forms. A module path **with** a version (`replace example.com/lib => example.com/ourfork/lib v1.4.2`) is fetched and checksum-verified like any other module, so it needs `go.sum` entries. A **filesystem path** (`replace example.com/lib => ../lib`) carries no version, is compiled straight from disk, and the target directory must contain its own `go.mod`. Two things surprise people: a `replace` does not add a requirement, so if nothing in the build list needs that module the line is simply inert; and only the main module's `replace` lines are honoured, so a library cannot redirect its importers.

code

mod · 14 lines
mod
module example.com/internal/billing

go 1.25

require (
	example.com/upstream/parser v1.4.2
	example.com/shared/auth v0.6.0
)

// Module form: a version is required, and go.sum gains entries for the fork.
replace example.com/upstream/parser => example.com/ourorg/parser-fork v1.4.2

// Directory form: no version, and ../auth must contain its own go.mod.
replace example.com/shared/auth => ../auth

go deeper

for a junior

Be ready to write both forms from memory and say what each right-hand side needs: a module path takes a version, a directory takes none and must hold its own go.mod.

for a middle

Explain that a replace rewrites the build list rather than adding to it, and describe what changes in go.sum for a module-path target versus a directory target.

for a senior

Show how you would verify a redirect actually fired — go list -m all printing the original => replacement pair — and why a directory replacement is fine in a branch but risky to merge.

for a principal

Own the policy question: which replaces are allowed into a repository at all, whether a filesystem path may ever reach the default branch, and how the team is stopped from shipping one by accident.

## What the directive is for A `go.mod` file records the module's own path, a language version, and a set of `require` lines naming the modules it depends on and the minimum version of each. Normally the go command resolves those requirements, downloads the exact versions from a module proxy, and compiles them. A `replace` directive interrupts that: it tells the go command that wherever the build would have used a particular module, it should use something else instead. The two things it is used for in practice are (1) developing two modules side by side, where you want your application to compile against the checkout of a library sitting next to it rather than a published release, and (2) shipping against a fork that carries a patch you need before upstream has released it. ## The syntax, both halves ``` replace old-module-path [old-version] => new-module-path new-version replace old-module-path [old-version] => ../some/directory ``` **Left-hand side.** The module path being redirected. The version is optional. `replace example.com/lib => …` redirects *every* version of `example.com/lib` that the graph resolves to. `replace example.com/lib v1.4.2 => …` redirects *only* v1.4.2 and leaves other versions alone — useful when you want the redirect to lapse automatically the moment resolution moves to a different version. **Right-hand side, module form.** `=> example.com/ourfork/lib v1.4.2`. A version is mandatory here. The replacement is downloaded and hashed exactly like any other dependency, so `go.sum` gains entries for the *replacement*, not for the module it stands in for. The replacement's own `go.mod` requirements are what feed the module graph from that point on. **Right-hand side, directory form.** `=> ../lib` or an absolute path. A version must **not** be given — the code on disk has no version. The directory must contain a `go.mod` file; a bare package directory is rejected. Nothing is downloaded and nothing is checksum-verified, which is exactly why this form is convenient during development and unwise to ship: the build now depends on a path outside the repository. ## Three rules that catch people out **A replace does not add a requirement.** If no `require` line and nothing in the transitive graph pulls in `example.com/lib`, then `replace example.com/lib => …` does nothing at all. It is a rewrite rule applied to the build list, not a way to introduce a dependency. There is no error and no warning for a replace that never fires, and `go mod tidy` will not remove it. **Only the main module's replaces apply.** The main module is the one containing the directory you invoke the go command from. Replace lines in the `go.mod` of any dependency are read and ignored. That makes the directive safe — a transitive dependency cannot silently swap out the source of code you compile — but it also means a published library whose build only works because of its own replace line is broken for everyone who imports it. **Versions on the right-hand side are used verbatim.** Minimal version selection does not run on a replacement target. Whatever version you write is the version compiled, even if a newer release of the fork exists. ## Editing without hand-writing the file `go mod edit` manipulates the directives programmatically, which is handy in scripts and CI: ``` go mod edit -replace=example.com/lib=example.com/ourfork/[email protected] go mod edit -replace=example.com/lib=../lib go mod edit -dropreplace=example.com/lib go mod edit -json # prints the whole file, directives included, as JSON ``` `go mod edit -json` is the quickest way to see the replace block a build is actually resolving with, which matters once a repository has accumulated several of them. ## Checking it took effect After adding a replace, `go list -m all` prints the build list with each replacement shown as `original => replacement`. If the line you added does not appear there, the module was not in the build list to begin with, and the redirect never fired.

  • Does a `replace` line make the go command fetch a module that nothing in your build requires?
    No. A replace is a rewrite applied to modules already in the build list. If nothing requires `example.com/lib`, the line simply never fires — no download, no error, no warning, and `go mod tidy` leaves the dead line in place. To bring a module in you still need a `require`.
  • How does `go.sum` differ between the two right-hand-side forms?
    A module-path replacement is downloaded and verified like any dependency, so `go.sum` needs hashes for the replacement module and version — not for the module it stands in for. A directory replacement is compiled from disk, so there is nothing to hash and no `go.sum` entry at all.
  • Can you redirect only one version of a dependency and leave the rest alone?
    Yes — put a version on the left: `replace example.com/lib v1.4.2 => example.com/ourfork/lib v1.4.2`. Only v1.4.2 is redirected. If resolution later moves the build to v1.4.3, the replacement stops applying, which makes the redirect self-expiring.

saying these in an interview costs you the question

  • Says a replace propagates to everyone importing the module
  • Puts a version on the right side of a directory replacement
  • Thinks the replaced directory does not need its own go.mod
  • Believes a replace alone pulls in an unrequired module
  • Claims minimal version selection runs on the replacement target
open as a page

What does `go mod tidy` change in a module's go.mod, and when do you run it?

level: juniorimportance: must knowfreq 78%

basics

~20 s

go mod tidy makes the require block match the code: it adds a requirement for every module your packages import, drops requirements nothing imports any more, maintains the indirect markers, and syncs go.sum. Run it after changing imports.

open as a page

Why must a Go module's path and its import paths end in /v2 once it releases v2.0.0?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Go treats each major version as a separate module. From v2 on, the module line in go.mod and every import of its packages must end in /v2, so v1 and v2 can live in one build.

open as a page

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

level: juniorimportance: must knowfreq 58%

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.

open as a page

If your go.mod requires example.com/lib v1.2.0 and v1.9.0 is now released, which version does `go build` use?

level: juniorimportance: must knowfreq 62%

basics

~20 s

The build uses v1.2.0. Go's minimal version selection takes the highest version some go.mod in the graph actually requires, never the newest one published. A new release stays invisible to the build until a require line names it.

open as a page

What does a go.sum line record, and how is go.sum different from go.mod?

level: middleimportance: must knowfreq 66%

basics

~10 s

go.sum records expected content hashes, normally two per module version: one over the module's file tree, one over its go.mod alone. It selects no versions, and a mismatch fails the build.

open as a page

Why must the module path declared in go.mod match the repository URL consumers import from?

level: middleimportance: must knowfreq 55%

basics

~20 s

The module path is the module's identity and its fetch address at once: the go command turns the import path into a repository location, then checks the downloaded go.mod declares that same path. A mismatch fails the build.

open as a page

What does vendor/modules.txt record, and why does a build fail when it disagrees with go.mod?

level: middleimportance: must knowfreq 48%

basics

~20 s

vendor/modules.txt indexes the vendored tree: each module, its selected version, whether go.mod requires it directly, and the packages copied from it. In vendor mode the go command compares that index with go.mod and refuses to build when the two disagree.

open as a page

What is GOPROXY, and where does the go command fetch modules from by default?

level: juniorimportance: should knowfreq 58%

basics

~20 s

GOPROXY lists the module servers the go command downloads dependencies from. It defaults to https://proxy.golang.org,direct: try Google's public module mirror first, and if the module is not there, fetch it straight from its version-control origin.

open as a page

How do you publish version v1.4.0 of a Go library so other teams can go get it?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Publishing a Go module means pushing a Git tag, not uploading an artifact. Commit a go.mod whose module path matches the repository URL, then push a tag named exactly v1.4.0. Consumers then request that module path at that version.

open as a page

What does `go mod vendor` create, and how does it change what `go build` uses?

level: juniorimportance: should knowfreq 52%

basics

~20 s

go mod vendor copies the source of every package needed to build and test the main module into a top-level vendor directory, plus an index file, vendor/modules.txt. Builds then compile from that directory instead of the downloaded module cache.

open as a page

What is a Go workspace, and what does the go.work file created by `go work init` do?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A Go workspace makes the go command treat several local module directories as the modules being built. go work init writes a go.work file listing those directories under use, so imports between them resolve to your local code instead of published versions.

open as a page

Why does a `replace` line in a dependency's go.mod have no effect on your build?

level: middleimportance: should knowfreq 48%

basics

~20 s

The go command honours replace and exclude directives only from the main module's go.mod, the one you build from. In a dependency they are read and ignored, so a library cannot force redirects on the modules that import it.

open as a page

For a Go module, what does `go list -m all` print that `go mod graph` does not?

level: middleimportance: should knowfreq 42%

basics

~20 s

go list -m all prints the build list: the one version of each module that the build actually uses. go mod graph prints raw requirement edges, one per line, and the same module can appear there at several versions, only one of which is selected.

open as a page

In a go.mod require line, what does the `// indirect` comment mean?

level: middleimportance: should knowfreq 60%

basics

~20 s

It marks a require line for a module that no package in your own module imports directly. The module is listed because something you depend on needs it, or because your module has to record a minimum version for it. The go command writes and removes the comment itself.

open as a page

In go.mod, what does a require line like example.com/lib v3.2.0+incompatible mean?

level: middleimportance: should knowfreq 42%

basics

~20 s

The +incompatible marker means the dependency is tagged at major version 2 or higher but has no go.mod file at that tag. Never being modules-aware, it is allowed to keep the bare path with no /v2 style suffix.

open as a page

What is Go's module graph pruning, and why does it add so many `// indirect` lines to go.mod?

level: middleimportance: should knowfreq 45%

basics

~20 s

Under a go 1.17 or higher directive the module graph keeps only each such module's immediate requirements, not its full transitive tail. In exchange, go.mod must list every module providing a transitively imported package — those are the // indirect lines.

open as a page

In the go command, what do -mod=mod, -mod=readonly and -mod=vendor do, and which applies by default?

level: middleimportance: should knowfreq 42%

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.

open as a page

In Go, if two dependencies require v1.2.0 and v1.5.0 of the same module, which one is selected and why?

level: middleimportance: should knowfreq 52%

basics

~20 s

v1.5.0 is selected. Minimal version selection walks the whole module graph and, for each module path, takes the maximum of the minimum versions required by any module in it. One version per path ends up in the build list.

open as a page

What is a Go pseudo-version like v0.0.0-20060102150405-abcdef123456, and when does the go command create one?

level: middleimportance: should knowfreq 44%

basics

~20 s

A pseudo-version is a synthetic semantic version the go command derives for a commit that has no suitable version tag. It encodes a base version, the commit's UTC timestamp, and a 12-character commit hash prefix, so untagged commits still order correctly.

open as a page

You published v1.4.0 of a Go module with a data-corrupting bug. How do you signal consumers not to use it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Add a retract directive naming that version to your module's go.mod, then tag and publish a newer version carrying it. The go command reads retractions from your highest version and stops selecting the retracted one, though the code stays downloadable.

open as a page

A pull request's go.mod lost a require line after `go mod tidy` — how do you decide it is safe?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Confirm the reason: tidy drops a requirement only when nothing in the module reaches that module any more, so look for the matching import removal in the same diff. Then re-run tidy yourself, check go mod why -m, and diff the build list before and after.

open as a page

How do you make the go command fetch a private module from an internal Git host?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Set GOPRIVATE to glob patterns matching your internal module path prefixes. Matching modules are then fetched directly from version control instead of the public proxy, and skipped by the public checksum database. Credentials remain git's job.

open as a page

Your published module's v1.3.0 tag was force-moved to a new commit and consumers now fail verification. Why?

level: seniorimportance: should knowfreq 45%

basics

~20 s

A published version is immutable by contract. Everyone who already fetched v1.3.0 recorded a hash of its content in go.sum, and the moved tag yields different bytes, so their build stops on a checksum mismatch. Fix it by publishing a new version.

open as a page

A Go build links both example.com/lib and example.com/lib/v2 — why do their types refuse to interoperate?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because a Go type's identity includes the import path of the package that defines it. Two majors are two module paths, so lib.Client and lib/v2.Client are unrelated types, with separate package-level state and separate sentinel errors.

open as a page

In a Go module, how do you force a transitive dependency up to a fixed version nothing else requires?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Run go get example.com/[email protected] in the main module. It writes a require line, marked // indirect, and version selection takes the highest required minimum, so the whole build moves to at least that version. Confirm with go list -m.

open as a page

Before cutting a release built offline from vendor/, how do you prove the vendored tree matches go.mod?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Re-run go mod vendor where the module sources are reachable, then confirm version control reports no change under vendor. That regeneration is the only proof the tree matches go.mod: the build's own check compares modules.txt with go.mod and never reads the copied files.

open as a page

Your Go build still selects v1.4.1 of a transitive module although v1.4.3 exists — why, and how do you confirm it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because no go.mod in the module graph names v1.4.3. Minimal version selection reads requirements, never the upstream tag list, so a published release is invisible until something requires it. Compare the selected build list against the versions that actually exist.

open as a page

Why can a module build inside your go.work workspace but fail in CI, and how do you confirm it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Workspace mode satisfies an import from a sibling directory in go.work's use list even when that module's own go.mod never requires it, so a missing requirement is invisible locally. CI builds the module alone and fails. Confirm by rebuilding with GOWORK=off.

open as a page

When a `replace` line points production builds at your team's fork, how long may that stand, and who can overrule it?

level: principalimportance: should knowfreq 38%

basics

~20 s

Treat a fork behind a replace line as time-boxed debt with a named owner and a written exit condition. Platform and security owners carry the divergence, from missed upstream patches to upgrade friction, so they get a veto on keeping it.

open as a page

showing 1–30 of 43