← Back to all articles

Semantic Versioning in Practice: MAJOR.MINOR.PATCH and Dependency Ranges

CLIBeginner

What each number means

PositionIncrement whenExamples
MAJORIncompatible API changeRemoving an interface, changing parameter meaning or response shape
MINORBackwards-compatible additionNew optional parameter, new endpoint
PATCHBackwards-compatible fixBug fix, docs, performance work that keeps semantics

The test is compatibility for consumers, not the size of the diff. Refactoring ten thousand lines with identical external behaviour is a PATCH. Remember who reads a version number: it answers "can I upgrade safely?", not "how big was the change?".

The 0.x phase and pre-releases

  • 0.x.y: the public API is not stable, so MINOR may contain breaking changes. Consumers should not assume 0.x upgrades are safe;
  • Pre-releases: 1.0.0-alpha.1, -rc.1 — they sort below the corresponding release;
  • Build metadata: 1.0.0+build.7 — ignored when comparing precedence.

Ranges in dependency declarations

SyntaxMeaning (given 1.2.3)Risk
1.2.3 (exact)Only that versionSafest, but patches need manual bumps
~1.2.3Any 1.2.x (patch level)Low
^1.2.3Any 1.x.y (minor level)Medium — depends on the upstream's discipline
* or latestWhatever existsHigh — builds are not reproducible

Note that ^0.2.3 usually means only 0.2.x, because MINOR can break compatibility during 0.x.

Four common traps

  1. Breaking changes shipped as MINOR: dependents break during routine upgrades — the most common breach of trust;
  2. Not committing lockfiles: without package-lock, Cargo.lock or go.sum there is no reproducible build;
  3. Overly wide ranges: one careless upstream release reaches everyone downstream;
  4. Using versions as marketing: jumping numbers for a "big release" destroys the meaning.

Making it stick

  • Have CI verify the version actually bumps with the change;
  • Adopt a commit convention so versions can be derived from history;
  • Publish a changelog per release, calling out breaking changes and migration steps;
  • Mark APIs deprecated and remove them only after at least one MINOR release.

Real-world cases: three upgrade incidents

  1. "A routine upgrade broke the service": the upstream shipped a breaking change as a MINOR. Fix: pin versions and route dependency upgrades through CI regression — do not blindly use ^.
  2. "Works locally but CI builds differently": no lockfile committed, so different versions got installed. Fix: commit the lockfile and make CI install deterministically.
  3. "Bumping the major for hype": semantics broken for marketing, so consumers cannot judge upgrade risk. Fix: increment strictly by compatibility.

Common questions

Do internal services need semver? Yes, especially with multiple consumers. A date-based scheme is fine internally, but be consistent within one system. What level is a security fix? The fix itself is a PATCH; if behaviour changes, it is a MAJOR. What if a dependency ignores semver? Pin the exact version and test manually when upgrading.

Version numbers at a glance

  • 1.4.2: major·minor·patch — a minor bump adds backward-compatible capability, a patch fixes issues compatibly;
  • 0.x is not stable: while the major is 0 any release may break; pin exact versions or commit a lockfile in production;
  • Pre-releases: 1.0.0-beta.1 sorts below 1.0.0, and +build metadata is ignored in comparisons;
  • Ranges: ^1.2.3 allows any 1.x, ~1.2.3 only 1.2.x, 1.2.3 pins exactly;
  • Commit the lockfile: without one, the same declarations can install differently on different machines.

Versioning inside the release flow

  • Bump on merge so one release maps to one change;
  • Group the changelog into added, fixed and breaking, with breaking items first and a migration example;
  • Publish immutable artefacts archived by version, and roll back by switching versions rather than rebuilding.

A cadence for dependency upgrades

Do not let upgrades pile up. Security patches should be applied promptly even within the same major; minor bumps can be batched weekly or per iteration; major upgrades get their own slot, reading the migration guide and filling test gaps first. Regular small batches beat a half-yearly leap.