How the version numbers work

A normal SemVer number follows the form X.Y.Z. X is the major version. Y is the minor version. Z is the patch version. Each part increases only when the corresponding type of change occurs. Major version zero is treated as initial development where anything may change. Once version 1.0.0 is released, the public API is considered stable and the increment rules apply strictly.

Patch releases fix incorrect behavior without changing the documented API. Minor releases add new functionality in a backward-compatible way or mark existing functionality as deprecated. Major releases introduce changes that break backward compatibility. These definitions come directly from the SemVer specification.

  • Increment patch for backward-compatible bug fixes only
  • Increment minor for new backward-compatible API features or deprecations
  • Increment major for any backward-incompatible API change
  • Reset lower numbers to zero after a higher-number bump

Dependency ranges and what they allow

Package managers use range operators to decide which versions satisfy a declared dependency. The caret operator ^ allows updates that do not change the leftmost non-zero digit. The tilde operator ~ allows only patch-level changes within the declared minor version. Exact pins require the precise version string. These ranges appear in files such as package.json and are evaluated by tools like npm.

A dependency declared as ^3.1.0 accepts 3.1.1 and 3.2.0 but rejects 4.0.0. This range works when the upstream project follows SemVer correctly. When the upstream project violates the rules, the range can pull in unexpected breaks.

  • ^4.17.21 accepts any 4.x release below 5.0.0
  • ~1.6.0 accepts only 1.6.x releases
  • Exact pins such as 4.18.2 accept only that single release
  • Wildcards such as 1.x accept any 1.x release

Where SemVer cannot protect you

SemVer assumes the declared public API matches actual behavior and that maintainers follow the rules. In practice, a patch release can introduce a regression that breaks an application even though the version number suggests safety. A minor release can change undocumented behavior that downstream code relied upon. These situations occur because the specification cannot enforce correct implementation.

Transitive dependencies add another layer of risk. A direct dependency may stay within its declared range while one of its own dependencies receives a breaking update. The resulting combination can fail even when every direct range appears conservative.

  • A patch can still contain a regression that affects your code
  • Undocumented behavior can change without a major bump
  • Transitive dependencies may break even when direct ranges are respected
  • Build metadata and pre-release tags do not affect precedence rules

Practical steps that reduce risk

Pin exact versions in lockfiles and separate those pins from the broader ranges declared in manifest files. Review lockfile diffs before merging updates. Run the full test suite after any dependency change, including transitive updates. Use tools that surface the complete dependency tree rather than only direct requirements.

When a new version appears, treat every update as potentially breaking regardless of the version number. Update in a controlled environment first. If tests pass, commit the new pins. If tests fail, investigate the root cause before widening the allowed range.

  • Keep manifest ranges separate from lockfile pins
  • Review every lockfile change before accepting it
  • Run tests that cover the code paths using the dependency
  • Document the reason for any manual version choice

Testing remains the final safeguard

No version scheme replaces verification. Even when every maintainer follows SemVer perfectly, the combination of libraries in a specific project can produce unexpected results. Automated tests, integration checks, and staged deployments catch problems that version numbers cannot signal. Teams that skip these steps eventually encounter breakage that a simple version bump would have avoided.

The same discipline applies when preparing contributions that others will consume. Clear changelogs, accurate version bumps, and published test results help downstream users decide whether an update is safe for them.