Is 1.0.0-rc.1 lower than 1.0.0? SemVer pre-release precedence and caret ranges
Why 1.0.0-rc.1 is lower than 1.0.0, and why ^1.2.3 does not select 1.5.0-beta.1, based on SemVer 2.0.0, node-semver and Cargo.
Which has higher precedence, 1.0.0-rc.1 or 1.0.0? And why does a dependency range of ^1.2.3 skip 1.5.0-beta.1?
The answers come from two different layers. Semantic Versioning 2.0.0 defines how versions compare. Package tools define range syntax such as ^1.2.3 and decide when pre-releases satisfy those ranges.
Normal versions compare numerically
A normal SemVer version has the form X.Y.Z. Each part is a non-negative integer without leading zeros. Precedence is determined by comparing major, minor and patch numerically from left to right.
This makes 1.10.0 higher than 1.9.0. A plain string sort can put them in the opposite order, so version lists need a SemVer comparison function rather than a lexical sort.
A hyphen introduces a pre-release
A hyphen after the patch number introduces a pre-release version.
1.0.0-alpha
1.0.0-beta.2
1.0.0-rc.1
When major, minor and patch are equal, a pre-release has lower precedence than the corresponding normal version.
1.0.0-rc.1 < 1.0.0
Pre-release identifiers are compared one at a time. Numeric identifiers compare as numbers. Other identifiers compare lexically in ASCII order. Numeric identifiers have lower precedence than non-numeric ones. If all preceding identifiers match, the version with more identifiers has higher precedence.
The SemVer specification gives this sequence:
1.0.0-alpha
< 1.0.0-alpha.1
< 1.0.0-alpha.beta
< 1.0.0-beta
< 1.0.0-beta.2
< 1.0.0-beta.11
< 1.0.0-rc.1
< 1.0.0
beta.2 is lower than beta.11 because 2 and 11 are separate numeric identifiers. By contrast, rc9 and rc10 are single alphanumeric identifiers. Lexical comparison puts rc10 below rc9. If a counter may reach two digits, rc.9 and rc.10 express the intended numeric order more clearly.
Build metadata does not affect precedence
Build metadata follows a plus sign:
1.0.0+build.1
1.0.0+build.2
SemVer ignores build metadata when determining precedence. These two versions therefore have equal precedence even though their build labels differ.
Why ^1.2.3 skips pre-releases
The SemVer specification does not define range syntax such as ^1.2.3. Tools define those rules.
In node-semver, the library used by npm, ^1.2.3 expands to:
>=1.2.3 <2.0.0-0
2.0.0-0 is the lowest valid pre-release of 2.0.0, so the upper bound also excludes pre-releases of 2.0.0.
That still does not fully explain why 1.5.0-beta.1 is excluded. By precedence alone, it lies between the bounds. node-semver has another rule: a pre-release satisfies a range by default only when at least one comparator has the same [major, minor, patch] tuple and includes a pre-release identifier.
The resulting checks are:
1.5.0-beta.1 satisfies ^1.2.3 → false
1.2.3-beta.4 satisfies ^1.2.3-beta.2 → true
1.2.4-beta.2 satisfies ^1.2.3-beta.2 → false
1.3.0 satisfies ^1.2.3-beta.2 → true
The includePrerelease option changes node-semver’s default exclusion of pre-releases. It does not remove the range’s explicit upper and lower bounds.
Cargo is similar, but its rules are separate
Cargo also excludes pre-releases unless the dependency requirement explicitly names one. If 1.0.0-alpha is the only published version, foo = "1.0" finds no matching package.
With foo = "1.0.0-alpha", Cargo can update to 1.0.0-beta, the normal release 1.0.0, or another compatible normal release such as 1.2.0. It does not update to 1.0.1-alpha or 1.0.1-beta.
Excluding pre-releases from a range is package-tool policy, not a rule in the SemVer specification. When using a different package manager, check that tool’s range documentation rather than assuming npm or Cargo behavior applies.
Specifications and documentation used
- Semantic Versioning 2.0.0
- node-semver README:
Caret RangesandPrerelease Tags - The Cargo Book — Specifying Dependencies: Pre-releases
Links and content were checked on 1 October 2026.