Semantic versioning

On this page 7

Semantic Versioning (often shortened to SemVer) is a convention for numbering software releases as MAJOR.MINOR.PATCH, in which each number signals what kind of change a release contains relative to a declared public API. In the software lifecycle, it turns a version number from an arbitrary label into a compatibility promise that people and dependency tools can act on.

The rules are written down in the Semantic Versioning 2.0.0 specification, published in 2013 and widely adopted by package ecosystems such as npm, Cargo and Go modules. The scheme only works when a project defines what its public API is, because every increment is judged against that contract and not against the size of the code change.

How the three version numbers work

Each of the three numbers answers one question about compatibility with the previous release. A project increments exactly one of them per release and resets the numbers to its right to zero, so 1.4.7 followed by a new feature becomes 1.5.0, not 1.5.7.

ComponentIncremented whenReset ruleConsumer impact
MAJORAn incompatible API change is releasedMINOR and PATCH reset to 0Code may need changes before upgrading
MINORBackward-compatible functionality is added or deprecatedPATCH resets to 0Safe to upgrade; new features available
PATCHBackward-compatible bug fixes onlyNothing resetsSafe to upgrade; behavior corrected
Meaning of each version component

Two further rules give the scheme its reliability. First, a published version is immutable: once 2.3.1 is released, its contents must never change, and any correction must ship as a new version. Second, numbers are non-negative integers without leading zeros and compare numerically, so 1.10.0 is newer than 1.9.0 even though a plain text sort would place it earlier.

Marking a feature as deprecated is itself a MINOR release, because existing callers still work. Actually removing the deprecated feature is a breaking change and requires a new MAJOR version, which is how SemVer connects to the separate lifecycle stages of deprecation and end of life.

Pre-release and build metadata labels

Beyond the core triple, the specification allows two optional suffixes. A pre-release label follows a hyphen (2.0.0-rc.1) and marks a version as unstable and lower in precedence than the final release. Build metadata follows a plus sign (2.0.0+20261109.sha.5114f85) and records build details that are ignored when comparing versions.

Pre-release identifiers are compared one dot-separated field at a time: numeric fields compare as numbers, alphanumeric fields compare in ASCII order, and a numeric field always ranks below an alphanumeric one. A longer list of identifiers ranks higher when all preceding fields are equal.

VersionTypical roleWhy it sorts here
1.0.0-alphaEarly internal testingShortest identifier list
1.0.0-alpha.1Second alpha buildExtra field ranks higher than none
1.0.0-beta.2Public beta“beta” sorts after “alpha” in ASCII
1.0.0-beta.11Later public beta11 compares numerically above 2
1.0.0-rc.1Release candidate“rc” sorts after “beta”
1.0.0Final releaseNo pre-release label ranks highest
Precedence of a release sequence, from lowest to highest

A frequent mistake is writing beta10 instead of beta.10. Without the dot, the field is compared as text, so beta10 sorts before beta9, which silently breaks upgrade ordering in package managers.

Version ranges and dependency resolution

The practical payoff of Semantic Versioning is that dependency managers can choose upgrades automatically. A project declares a range rather than a single version, and the resolver picks the newest release inside that range, trusting that MINOR and PATCH releases do not break callers.

Declared rangeAccepted versionsTypical use
^1.4.2 (caret)>=1.4.2 and <2.0.0Default for stable libraries
~1.4.2 (tilde)>=1.4.2 and <1.5.0Accept bug fixes only
^0.4.2 (caret on 0.x)>=0.4.2 and <0.5.0Pre-1.0 libraries treat MINOR as breaking
1.4.2 (exact)Only 1.4.2Pinning a known-good version
Common range operators in npm-style syntax

Ranges cover what may be installed, while a lockfile records what was actually installed. Combining a caret range with a committed lockfile gives reproducible builds today and controlled upgrades later. Go modules take a stricter line: from version 2 onward, the major version becomes part of the module import path (for example /v2), so two major versions can coexist in one build.

Semantic Versioning compared with other schemes

SemVer is designed for libraries and APIs consumed by other code. Products used directly by people often prefer schemes that communicate release date or support status instead of API compatibility.

SchemeExampleWhat the number conveysBest fit
Semantic Versioning3.2.1Compatibility of the public APILibraries, SDKs, packages
Calendar versioning24.04Release year and monthOperating systems, time-based releases
Sequential numberingBuild 2417Order of builds onlyInternal builds, firmware
Marketing versionsVersion 11Product generationEnd-user applications
Versioning schemes and what they communicate

Many projects mix schemes: an application may show a marketing version to users while its plugin API follows SemVer. Long-term support releases and release candidates also map naturally onto SemVer, with an LTS line receiving only PATCH releases for years and release candidates expressed as pre-release labels.

Limitations and common mistakes

Semantic Versioning is a social contract, not a technical guarantee. Nothing stops a maintainer from shipping a breaking change in a PATCH release, and observable behavior that callers rely on often extends beyond the documented API.

ProblemCausePrevention
Breaking change in a MINOR releaseNo clear definition of the public APIDocument the API and run API-diff tools in CI
Project stuck at 0.x for yearsFear of committing to stabilityRelease 1.0.0 once real users depend on it
Republished version with new contentsFixing a release in placeTreat releases as immutable; publish a PATCH
Resolver installs an unexpected releaseWrong pre-release identifier formatUse dot-separated numeric fields
Typical failures and how to prevent them
  • Version 0.y.z means anything may change at any time; the stability promise starts only at 1.0.0.
  • A security fix that changes behavior may still require a MAJOR version if callers must adapt.
  • Bumping MAJOR is not a failure; frequent honest MAJOR releases are better than hidden breakage.

Stripped to the essentials

A version number such as 4.2.1 is a short message from the people who build a piece of software. The last number says they fixed something. The middle number says they added something new without breaking anything. The first number says something changed in a way that may require you to adapt.

Think of it as a label on a replacement part: one label means “fits exactly as before”, another means “fits and does more”, and a third means “check before installing”. The key point is trust: the numbers are useful only when the builders follow the rules every time.

Where it is applied

A team maintains an internal payment library used by 40 services across a company. The library sits at version 2.6.3, and every service declares the range ^2.6.0, so automated dependency updates pull in new 2.x releases without manual review.

When the team fixes a rounding error in currency conversion, it ships 2.6.4, and all services receive the fix within a day. When it adds support for a new payment method, it ships 2.7.0. When a regulatory change forces it to replace the transaction function with a new signature, it ships 3.0.0 and keeps a 2.x line alive with PATCH releases for six months.

Because the services declared ^2.6.0, none of them upgrades to 3.0.0 by accident. Each owning team migrates on its own schedule, and the breaking change causes no production incident, which is exactly the outcome the numbering scheme is designed to produce.