Context
I have about 4-5 years of Python experience, so this article compares Go and Python from that perspective. I like both languages. This is not "Python bad, Go good". I just want to understand Go's design choices.
Go Modules in One Sentence
Go favors simplicity and reproducibility over maximum flexibility.
Why Go Uses Module Paths
In Go, dependencies are identified by module paths (for example, github.com/gin-gonic/gin) instead of global package names.
- No central package-name namespace is required.
- Name collisions are rare because the full path is the identity.
- The source location and package identity are aligned.
Basic Usage
Add a dependency:
go get github.com/gin-gonic/gin
Import it:
import "github.com/user/echo-server/auth"
For major versions v2+, the version is part of the import path:
import "github.com/labstack/echo/v4"
This is called Semantic Import Versioning and helps isolate breaking changes.
How Dependency Resolution Works in Go
Go uses Minimal Version Selection (MVS).
A practical rule of thumb:
- For each module in the graph, Go selects the highest required version it encounters.
- Across the full build, this produces a deterministic result from
go.modand the module graph.
Example:
A
├─ B v1.2
└─ C v1.1
C
└─ B v1.3
Result:
B v1.3
C v1.1
A can still use B v1.3 because, within the same major version, v1.3 is expected to remain backward compatible with v1.2.
Why Go Avoided a SAT-Style Solver
Python/npm-style dependency solving is generally constraint solving (SAT-like):
A requires B >=1.2 <2
C requires B >=1.5
D requires B <1.7
The resolver explores candidates (B=1.5, B=1.6, etc.).
That can improve flexibility, but it also increases complexity.
Go intentionally chose a different model:
- Keep resolution predictable and fast.
- Avoid heavy search in the resolver.
- Make builds more reproducible from the same module inputs.
- Push compatibility responsibility to library authors.
This design aligns with:
predictability > flexibility
Trade-Offs vs Python/npm
Python/npm approach:
- More flexible constraints.
- Smarter solver behavior can find compatible sets.
- Often needs lockfiles for strong reproducibility.
Go approach:
- Simpler dependency model.
- Deterministic selection behavior.
- Faster, less solver complexity.
- Strong convention: avoid breaking changes within a major version.
Summary
Go rejected SAT-style dependency resolution because the Go team prioritized:
- Simplicity
- Predictability
- Reproducible builds
- Fast dependency resolution
That is why Go modules combine:
- Minimal Version Selection (MVS)
- Semantic Import Versioning
The core idea is not maximum flexibility. It is reliable, understandable dependency management.