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.
| Component | Incremented when | Reset rule | Consumer impact |
|---|---|---|---|
| MAJOR | An incompatible API change is released | MINOR and PATCH reset to 0 | Code may need changes before upgrading |
| MINOR | Backward-compatible functionality is added or deprecated | PATCH resets to 0 | Safe to upgrade; new features available |
| PATCH | Backward-compatible bug fixes only | Nothing resets | Safe to upgrade; behavior corrected |
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.
| Version | Typical role | Why it sorts here |
|---|---|---|
| 1.0.0-alpha | Early internal testing | Shortest identifier list |
| 1.0.0-alpha.1 | Second alpha build | Extra field ranks higher than none |
| 1.0.0-beta.2 | Public beta | “beta” sorts after “alpha” in ASCII |
| 1.0.0-beta.11 | Later public beta | 11 compares numerically above 2 |
| 1.0.0-rc.1 | Release candidate | “rc” sorts after “beta” |
| 1.0.0 | Final release | No pre-release label ranks 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 range | Accepted versions | Typical use |
|---|---|---|
^1.4.2 (caret) | >=1.4.2 and <2.0.0 | Default for stable libraries |
~1.4.2 (tilde) | >=1.4.2 and <1.5.0 | Accept bug fixes only |
^0.4.2 (caret on 0.x) | >=0.4.2 and <0.5.0 | Pre-1.0 libraries treat MINOR as breaking |
1.4.2 (exact) | Only 1.4.2 | Pinning a known-good version |
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.
| Scheme | Example | What the number conveys | Best fit |
|---|---|---|---|
| Semantic Versioning | 3.2.1 | Compatibility of the public API | Libraries, SDKs, packages |
| Calendar versioning | 24.04 | Release year and month | Operating systems, time-based releases |
| Sequential numbering | Build 2417 | Order of builds only | Internal builds, firmware |
| Marketing versions | Version 11 | Product generation | End-user applications |
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.
| Problem | Cause | Prevention |
|---|---|---|
| Breaking change in a MINOR release | No clear definition of the public API | Document the API and run API-diff tools in CI |
| Project stuck at 0.x for years | Fear of committing to stability | Release 1.0.0 once real users depend on it |
| Republished version with new contents | Fixing a release in place | Treat releases as immutable; publish a PATCH |
| Resolver installs an unexpected release | Wrong pre-release identifier format | Use dot-separated numeric fields |
- 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.