# Semantic Versioning: 10 Edge Cases You Can’t Afford to Miss
> 10 real-world edge cases in Semantic Versioning that cause confusion, break builds, or violate the spec, essential for anyone managing software releases.

Canonical: https://blog.abhimanyu-saharan.com/posts/semantic-versioning-10-edge-cases-you-can-t-afford-to-miss
Published: 2025-05-18
Last updated: 2025-05-24
Authors: Abhimanyu Saharan
Categories: Software Engineering

In modern software development, maintaining a clean and consistent versioning strategy is not just good practice, it’s essential. Whether you're an open-source maintainer, enterprise developer, or DevOps engineer, **Semantic Versioning (SemVer)** provides a predictable and reliable framework for communicating changes in your codebase.

## The Problem: Dependency Hell

As projects grow in complexity and adopt more third-party packages, managing dependencies becomes a challenge. Poor versioning leads to two major issues:

- **Version Lock**: You can’t update a package without modifying all dependents.
- **Version Promiscuity**: You assume compatibility with future versions that break your software.

The result? Welcome to **dependency hell, **a frustrating state where updates are risky, slow, and fragile.

## The Solution: Semantic Versioning

Semantic Versioning introduces a structured format to version numbers and ties those versions directly to changes in your public API.

### The Format: `MAJOR.MINOR.PATCH`

- **MAJOR**: Incremented for incompatible API changes.
- **MINOR**: Incremented for backward-compatible feature additions.
- **PATCH**: Incremented for backward-compatible bug fixes.

**Example Progression**:

```
1.0.0 → 1.0.1 → 1.1.0 → 2.0.0
```

Each number conveys a specific level of impact and intent.

## Declaring a Public API

SemVer starts with clearly defining what constitutes your **public API, **whether it's in code annotations or documentation. Changes to this API are what determine version updates. Without a public API, you cannot apply SemVer meaningfully.

### Initial Development (`0.y.z`)

During early stages, anything can change. Use version `0.1.0`, `0.2.0`, etc. to indicate pre-release, unstable versions.

## Pre-Releases and Metadata

- **Pre-release** identifiers: `-alpha`, `-beta.2`, `-rc.1`
- **Build metadata**: `+001`, `+sha.5114f85`

Examples:

- `1.0.0-alpha+001`
- `2.1.0-beta.11+build.20240518`

Pre-release versions have **lower precedence** than their final releases.

## Version Precedence

When comparing versions:

```
1.0.0 < 1.1.0 < 2.0.0
1.0.0-alpha < 1.0.0
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0
```

Numeric identifiers are compared numerically; alphanumerics are compared lexically.

## Best Practices

- Do **not** modify a published version.
- Deprecations should go in a minor release before being removed in a major release.
- Always test downstream compatibility before publishing a breaking change.

## Benefits of SemVer

By adhering to SemVer:

- Consumers can safely update without surprises.
- Tools and build systems can reason about version compatibility.
- Teams can coordinate releases more effectively.

Let’s say your library `Firetruck` depends on `Ladder@3.1.0`. You can safely define:

```
>=3.1.0 <4.0.0
```

That way, any patch or minor version upgrade is implicitly trusted to maintain compatibility.

### **10 Edge Cases You Can’t Afford to Miss**

### 1. **Patch Release Contains a Breaking Change**

**Mistake**:

```
1.2.3 → 1.2.4  
```

But `1.2.4` breaks the public API.

**Impact**:  
Clients trusting the patch update for safety are unexpectedly broken.

**Correct**:

```
1.2.3 → 2.0.0  
```

### 2. **Minor Release Removes or Alters Existing Behavior**

**Mistake**:

```
1.4.0 → 1.5.0  
```

`1.5.0` removes a parameter or changes the return type of a function.

**Impact**:  
Backwards-incompatible behavior introduced silently.

**Correct**:

```
1.4.0 → 2.0.0
```

### 3. **Pre-Release (`-alpha`, `-rc`) Incorrectly Assumed Stable**

**Scenario**:  
A client assumes `1.0.0-rc.1` is safe and deploys it to production.

**Problem**:  
Pre-releases are _not_ guaranteed to follow compatibility rules.

**Best Practice**:  
Clearly document that pre-release versions are unstable and avoid them in production.

### 4. **0.y.z Treated as Stable**

**Example**:  
A library at `0.9.5` breaks APIs frequently, but consumers treat it as stable.

**Reality**:  
According to SemVer:

> CAUTION
> Anything MAY change at any time

**Best Practice**:  
Communicate instability of 0.x versions clearly. Reach `1.0.0` once API expectations are consistent.

### 5. **Build Metadata Misused for Compatibility Checks**

**Example**:

```
1.0.0+build1234 ≠ 1.0.0+build5678
```

**Problem**:  
Some tooling treats different build metadata as different versions.

**SemVer Rule**:

Build metadata **must be ignored** when determining version precedence.

### 6. **Incorrectly Parsing Precedence in Sorting**

**Bad Sort**:

```
1.0.0
1.0.0-beta
1.0.0-alpha
```

**Correct Order**:

```
1.0.0-alpha < 1.0.0-beta < 1.0.0
```

### 7. **Using `v1.2.3` as a SemVer**

**SemVer Rule**:  
`v1.2.3` is not a semantic version, it’s a common tag prefix.

**Impact**:  
String parsing may fail if tooling expects raw SemVer.

**Best Practice**:  
Use `1.2.3` in version constraints. Use `v1.2.3` only for VCS tagging.

### 8. **Re-Releasing a Version with Modified Code**

**Scenario**:  
Tag `1.2.0` is updated with a new commit without changing the version number.

**Impact**:  
Breaks reproducibility and violates SemVer’s immutability.

**SemVer Rule**:

Once a version is released, its contents must not change.

### 9. **Changing Private/Internal Logic Without Version Bump**

**Allowed**:  
If the public API remains unchanged, private refactors do _not_ require version bumps.

**Edge Case**:  
If internal changes impact API behavior (e.g., timing, side effects), treat as a patch or minor bump depending on severity.

### 10. **Skipping Major Versions for Marketing**

**Example**:  
Project jumps from `2.5.1` to `10.0.0` for hype.

**SemVer Guidance**:  
There’s no technical violation _if_ the version change reflects breaking API changes, but doing it for non-technical reasons can confuse clients and violate trust.

## Conclusion

Semantic Versioning gives software maintainers a transparent and standardized way to manage releases and communicate changes. It reduces surprises, enforces discipline, and fosters trust between developers and consumers.

If you want to adopt SemVer:

- Start by declaring your public API.
- Follow the versioning rules.
- Link to the [official specification](https://semver.org) in your README.

Let version numbers tell the story, clearly, consistently, semantically.

## FAQ

### What is Semantic Versioning (SemVer) and why is it important?

Semantic Versioning is a standardized versioning system that uses the format `MAJOR.MINOR.PATCH` to communicate changes in a project’s public API. It reduces upgrade risk by clarifying what changes are backward-compatible and which are breaking, helping teams manage dependencies more predictably.

### What does a version like 0.9.5 imply under SemVer?

Versions in the `0.y.z` range are considered unstable. **Anything may change at any time**, and no guarantees of compatibility are made. Consumers should avoid treating such versions as production-stable.

### Can I modify or re-release a version like 1.2.0 after publishing it?

No. Under SemVer, once a version is published, its contents **must not change**. Modifying a published version violates immutability guarantees and breaks reproducibility for consumers.

### Do pre-releases like 1.0.0-rc.1 follow the same compatibility rules as stable versions?

No. Pre-releases are **not subject to compatibility guarantees**. They are intended for testing and validation and should not be used in production without caution.

### What are some common mistakes when applying SemVer?

- Issuing breaking changes in patch or minor releases
- Treating `0.x` versions as stable
- Using build metadata (`+meta`) in compatibility checks
- Re-tagging released versions with different code
- Relying on `v1.2.3` (with prefix) for version comparison instead of `1.2.3`
