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.

DevelopmentVersioningSemVerDependencies

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

Links and content were checked on 1 October 2026.

Back to the blog