Skip to content
AgileLogics
Programming Languages

Understanding Semantic Versioning

MAJOR.MINOR.PATCH looks simple, but the rules behind it are what let dependency managers upgrade packages safely and automatically.

2 min read · By James Okafor · Aug 10, 2026

Semantic Versioning (SemVer) defines version numbers as MAJOR.MINOR.PATCH, where each position has a precise, binding meaning rather than being an arbitrary release counter.

The three numbers

  • MAJOR — incremented for incompatible API changes. Upgrading across a major version may break existing code.
  • MINOR — incremented for new, backwards-compatible functionality. Existing code keeps working after the upgrade.
  • PATCH — incremented for backwards-compatible bug fixes only. No new functionality, no breaking changes.

So 2.4.1 to 2.5.0 means new features were added but nothing should break; 2.4.1 to 3.0.0 means something incompatible changed and upgrading needs review.

Why dependency managers rely on this

Tools like npm, Composer, and Cargo let you constrain a dependency to a range, e.g. ^2.4.1 (in npm/Composer terms, "compatible with 2.4.1, meaning any 2.x.x version that's >= 2.4.1"). This only works safely because SemVer makes a binding promise: if a package author follows it correctly, any 2.x.x release is guaranteed not to break code written against 2.4.1. That promise is exactly what lets npm install or composer update pull in newer patch and minor versions automatically without manual review, while still refusing to silently cross a major version boundary.

Pre-1.0.0 and pre-release versions

Versions below 1.0.0 are considered initial development, where anything may change at any time — there's no compatibility promise yet. Pre-release versions append a hyphen suffix, like 2.0.0-beta.1, and are considered lower precedence than the associated normal release (2.0.0-beta.1 < 2.0.0).

The catch

SemVer is a convention, not something a package manager can enforce automatically — it depends entirely on the package author correctly judging whether a change is actually breaking. A patch release that accidentally breaks something is a real, if hopefully rare, failure mode, which is exactly why lockfiles (package-lock.json, composer.lock) exist: to pin the exact versions that were tested together, rather than trusting version ranges alone in production.

Versioning Dependency Management