Real-world analogy
Think of versioning like book editions. A 2nd Edition textbook has updates, corrections, or new chapters compared to the 1st. The edition number tells you what to expect before you open the book.
A comprehensive guide to software and package versioning — from beginner concepts to advanced strategies.
Software changes over time. Versioning is how developers communicate change to other developers (and their future selves). A version number is a label that answers:
Real-world analogy
Think of versioning like book editions. A 2nd Edition textbook has updates, corrections, or new chapters compared to the 1st. The edition number tells you what to expect before you open the book.
The most common format you’ll see is:
MAJOR.MINOR.PATCHExample: 2.4.1
| Part | Number | Meaning |
|---|---|---|
| MAJOR | 2 | Big change — may break existing code |
| MINOR | 4 | New feature — backward compatible |
| PATCH | 1 | Bug fix — no new features |
This format is called Semantic Versioning (SemVer) and is the industry standard.
{ "dependencies": { "react": "^18.2.0", "lodash": "~4.17.21", "axios": "1.6.0" }}django==4.2.0requests>=2.28.0numpy~=1.24.0[dependencies]serde = "1.0"tokio = { version = "1", features = ["full"] }{ "require": { "laravel/framework": "^10.0", "guzzlehttp/guzzle": "^7.5" }}| Symbol | Example | Meaning |
|---|---|---|
^ | ^1.2.3 | Allow MINOR and PATCH updates (>=1.2.3 <2.0.0) |
~ | ~1.2.3 | Allow PATCH updates only (>=1.2.3 <1.3.0) |
>= | >=1.2.0 | Any version equal to or greater |
= | =1.2.3 | Exactly this version only |
* | * | Any version (avoid in production!) |
- | 1.2.0 - 2.0.0 | Range between two versions (inclusive) |
Semantic Versioning (semver.org) defines a strict contract:
0.0.Full version string format:
MAJOR.MINOR.PATCH[-pre-release][+build-metadata]Examples:
1.0.0-alpha1.0.0-alpha.11.0.0-beta.21.0.0-rc.11.0.01.0.11.1.02.0.02.0.0+build.20240101| Identifier | Purpose | Example |
|---|---|---|
alpha | Early internal testing, unstable | 2.0.0-alpha.1 |
beta | Feature-complete, public testing | 2.0.0-beta.3 |
rc (Release Candidate) | Final testing before stable release | 2.0.0-rc.1 |
+build | Build metadata (ignored in comparisons) | 1.0.0+20240501 |
0.x.x — Special RulesWhile your project is in initial development, MAJOR version zero (0.y.z) has special meaning:
0.1.0 → 0.2.0 may include breaking changes.When to go 1.0.0
Release 1.0.0 when your public API is stable, you have real users depending on it, and you’re committed to backward compatibility.
Lock files record the exact resolved version of every dependency (including transitive ones). They ensure every developer and CI environment installs identical code.
| Package Manager | Lock File |
|---|---|
| npm | package-lock.json |
| Yarn | yarn.lock |
| pnpm | pnpm-lock.yaml |
| pip | requirements.txt (pinned) / poetry.lock |
| Cargo | Cargo.lock |
| Bundler (Ruby) | Gemfile.lock |
| Composer (PHP) | composer.lock |
Audit your current dependencies for known vulnerabilities:
npm auditpip-auditcargo auditCheck outdated packages:
npm outdatedpip list --outdatedcargo outdatedUpdate PATCH/MINOR (generally safe):
npm updatepip install --upgrade <package>cargo updateReview changelogs before updating MAJOR versions.
Run your test suite after every update.
A commit message convention that drives automated versioning:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]| Type | Triggers | Example |
|---|---|---|
fix: | PATCH bump | fix: handle null pointer in auth |
feat: | MINOR bump | feat: add OAuth2 login support |
BREAKING CHANGE: | MAJOR bump | feat!: remove deprecated v1 API |
docs: | No release | docs: update README |
chore: | No release | chore: upgrade devDependencies |
refactor: | No release | refactor: simplify token parsing |
perf: | PATCH bump | perf: cache user session lookups |
Example commit with breaking change:
feat!: migrate config format to YAML
BREAKING CHANGE: JSON config files are no longer supported.Migrate your config.json to config.yaml before upgrading.semantic-releasesemantic-release fully automates version management:
Basic .releaserc.json:
{ "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/changelog", "@semantic-release/npm", "@semantic-release/github", [ "@semantic-release/git", { "assets": ["CHANGELOG.md", "package.json"], "message": "chore(release): ${nextRelease.version} [skip ci]" } ] ]}Follow the Keep a Changelog format:
# Changelog
All notable changes to this project will be documented here.
## [Unreleased]
## [2.1.0] - 2024-06-01### Added- OAuth2 login support (#142)
### Fixed- Null pointer exception in token refresh (#138)
## [2.0.0] - 2024-05-01### Breaking Changes- Removed deprecated v1 REST endpoints- Config format changed from JSON to YAML
### Added- New GraphQL APIManaging multiple packages in a single repository requires specialized tooling.
Changesets (JS/TS)
Recommended for npm monorepos. Developers add “changeset” files describing their changes; releases are batched.
bash npx changeset add npx changeset version npx changeset publish
Lerna
Classic JS monorepo tool. Supports independent or fixed versioning across packages.
bash lerna version lerna publish
Nx Release
Built into Nx workspaces. Supports conventional commits and programmatic release management.
bash nx release version nx release publish
Cargo Workspaces
Rust workspaces share a Cargo.lock. Use cargo-workspaces crate for coordinated releases.
bash cargo workspaces version patch cargo workspaces publish
| Strategy | Best For | Pros | Cons |
|---|---|---|---|
| SemVer | Libraries, public APIs | Universal standard, clear intent | Requires discipline |
| CalVer | Applications, OS distros | Date conveys release timing | Doesn’t communicate impact |
| GitHash | Internal/microservices | Always unique, traceable | No human-readable meaning |
| Sequential | Simple internal tools | Simple to implement | No semantic information |
| Romantic | Consumer products (e.g. Ubuntu) | Memorable codenames | Requires parallel numeric version |
CalVer format examples:
Ubuntu: 22.04.3 LTS → YYYY.MM.PATCHpip: 23.3.1 → YY.MINOR.PATCHBlack: 24.4.2 → YY.MM.PATCHWhen your software exposes an API, versioning strategy impacts clients directly.
GET /api/v1/users GET /api/v2/users
✅ Explicit, easy to test
❌ URL bloat, hard to deprecate
http GET /api/users Accept: application/vnd.myapi.v2+json
✅ Clean URLs
❌ Less visible, harder to test in browser
GET /api/users?version=2
✅ Easy for clients
❌ Can be accidentally omitted
Responsible versioning includes a clear deprecation lifecycle:
Example deprecation notice in code:
/** * @deprecated since v3.0.0 — use `authenticateWithOAuth()` instead. * Will be removed in v4.0.0. */export function legacyLogin(username: string, password: string) { console.warn('[DEPRECATED] legacyLogin() will be removed in v4.0.0.'); // ...}A production-grade automated release pipeline:
name: Release
on: push: branches: [main]
jobs: release: runs-on: ubuntu-latest permissions: contents: write issues: write pull-requests: write
steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for semantic-release
- uses: actions/setup-node@v4 with: node-version: 20
- run: npm ci
- run: npm test
- name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-releaseDid you change the public API in a breaking way? ├── YES → Bump MAJOR (e.g. 1.4.2 → 2.0.0) └── NO → Did you add new functionality? ├── YES → Bump MINOR (e.g. 1.4.2 → 1.5.0) └── NO → Bump PATCH (e.g. 1.4.2 → 1.4.3)# npm — interactive version bumpnpm version patch # 1.0.0 → 1.0.1npm version minor # 1.0.0 → 1.1.0npm version major # 1.0.0 → 2.0.0npm version prerelease --preid=beta # 1.0.0 → 1.0.1-beta.0
# Python (Poetry)poetry version patchpoetry version minorpoetry version major
# Cargo (Rust) — edit Cargo.toml, then:cargo build # validates new version