Versioning Conventions
Why Versioning Matters
Version numbers aren’t just arbitrary labels—they communicate important information about your software. When you see v2.0.0 vs v1.2.3, you should immediately know something significant changed in version 2.
Good versioning helps everyone understand:
- Users: Whether they can safely upgrade
- Contributors: What kind of changes are appropriate for a release
- Maintainers: How to communicate the impact of changes
Without a versioning system, every update is a mystery. With semantic versioning, version numbers tell a story about your project’s evolution.
Semantic Versioning (SemVer) TL;DR
We use Semantic Versioning 2.0.0 for all SEAD Club projects.
Format: MAJOR.MINOR.PATCH (e.g., 1.4.2)
When to increment each number:
MAJOR (1.x.x → 2.x.x) - Breaking changes (backwards incompatible)
MINOR (x.1.x → x.2.x) - New features (backwards compatible)
PATCH (x.x.1 → x.x.2) - Bug fixes (backwards compatible)Examples:
- Fixed a bug? →
1.2.3→1.2.4 - Added a new feature? →
1.2.4→1.3.0 - Changed the API in a breaking way? →
1.3.0→2.0.0
That’s the essence. Read on for details and edge cases.
The Full Semantic Versioning Spec
Version Number Format
A version number MUST take the form X.Y.Z where:
X= MAJOR version (backwards incompatible changes)Y= MINOR version (new features, backwards compatible)Z= PATCH version (bug fixes, backwards compatible)
Each number:
- Must be a non-negative integer
- Must NOT have leading zeros
- Must increase numerically (1.9.0 → 1.10.0 → 1.11.0, not 1.9.0 → 1.10.0 → 1.9.1)
MAJOR Version (Breaking Changes)
Increment MAJOR when you make backwards-incompatible changes to your public API.
Examples of breaking changes:
- Removing a function or method
- Changing function signatures (parameters, return types)
- Renaming public properties or methods
- Changing expected behavior in ways that break existing code
- Removing or renaming configuration options
Example:
// v1.x.x
function calculatePrice(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// v2.0.0 - Breaking: now requires tax parameter
function calculatePrice(items, taxRate) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 + taxRate);
}Users relying on the old signature will get errors, so this requires a major bump.
When you increment MAJOR:
- Reset MINOR and PATCH to 0
- Example:
1.9.8→2.0.0
MINOR Version (New Features)
Increment MINOR when you add functionality in a backwards-compatible way.
Examples:
- Adding a new function or method
- Adding new optional parameters (with defaults)
- Adding new configuration options
- Marking functionality as deprecated (but not removing it yet)
- Substantial internal improvements that don’t change the API
Example:
// v1.2.x
function calculatePrice(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// v1.3.0 - New feature: optional discount
function calculatePrice(items, discount = 0) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal * (1 - discount);
}
// Old code still works: calculatePrice(items)
// New code can use: calculatePrice(items, 0.1)
When you increment MINOR:
- Reset PATCH to 0
- Keep MAJOR the same
- Example:
1.2.8→1.3.0
PATCH Version (Bug Fixes)
Increment PATCH when you make backwards-compatible bug fixes.
A bug fix is an internal change that fixes incorrect behavior.
Examples:
- Fixing a crash or error
- Correcting a calculation
- Fixing unexpected behavior
- Performance improvements that don’t change the API
- Security patches
Example:
// v1.2.3 - Bug: doesn't handle empty arrays
function calculatePrice(items) {
return items.reduce((sum, item) => sum + item.price, 0);
}
// v1.2.4 - Fixed: now handles empty arrays
function calculatePrice(items) {
if (items.length === 0) return 0;
return items.reduce((sum, item) => sum + item.price, 0);
}When you increment PATCH:
- Keep MAJOR and MINOR the same
- Example:
1.2.3→1.2.4
Special Version Cases
Version 0.x.x (Initial Development)
Before v1.0.0: Anything goes. Your API isn’t stable yet.
During initial development (v0.x.x):
- Increment MINOR for new features or breaking changes
- Increment PATCH for bug fixes
- Don’t worry too much about breaking changes—you’re still figuring things out
Example progression:
0.1.0 - Initial release
0.2.0 - Added user authentication (breaking change from 0.1.0, but that's ok)
0.2.1 - Fixed login bug
0.3.0 - Added profile system
1.0.0 - First stable release with defined APIWhen to release 1.0.0:
- Your software is being used in production
- You have a stable API that users depend on
- You’re ready to commit to backwards compatibility
Pre-release Versions
For alpha, beta, or release candidate versions, append a hyphen and identifier:
Format: MAJOR.MINOR.PATCH-identifier
Examples:
1.0.0-alpha - Early testing version
1.0.0-alpha.1 - First alpha iteration
1.0.0-beta - Beta version
1.0.0-beta.2 - Second beta iteration
1.0.0-rc.1 - Release candidate 1
1.0.0 - Stable releasePrecedence (lowest to highest):
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-rc.1 < 1.0.0When to use:
- Testing a major release before making it official
- Getting early feedback on new features
- Internal testing of potentially unstable code
Build Metadata
Append a plus sign for build information (doesn’t affect version precedence):
Examples:
1.0.0+20241220
1.0.0+sha.5114f85
1.0.0-beta+exp.sha.5114f85Build metadata is informational only. 1.0.0+001 and 1.0.0+002 are considered equivalent versions.
Practical Examples
Scenario 1: Adding a Feature
Current version: 1.2.3
You add a new function exportToCSV() that doesn’t change any existing functionality.
New version: 1.3.0 (MINOR bump)
Scenario 2: Fixing a Bug
Current version: 1.3.0
You fix a crash when users enter special characters.
New version: 1.3.1 (PATCH bump)
Scenario 3: Breaking Change
Current version: 1.3.1
You change the database schema in a way that requires users to run migrations and update their code.
New version: 2.0.0 (MAJOR bump)
Scenario 4: Multiple Changes
You’ve made several changes:
- Added two new features
- Fixed three bugs
- Made one breaking change
New version: Increment MAJOR (because of the breaking change)
- If current is
1.3.1→ new version is2.0.0
All the features and fixes are included in the major release.
Scenario 5: Deprecation
Current version: 1.4.0
You want to remove oldFunction() but users still depend on it.
Step 1: Mark it deprecated in 1.5.0
/**
* @deprecated Use newFunction() instead. Will be removed in v2.0.0
*/
function oldFunction() {
console.warn('oldFunction is deprecated, use newFunction');
return newFunction();
}Step 2: Remove it in 2.0.0
This gives users time to migrate before the breaking change.
How to Apply Versions in Your Projects
In package.json (JavaScript/Node.js)
{
"name": "my-project",
"version": "1.3.0",
...
}Update with: npm version major|minor|patch
In Cargo.toml (Rust)
[package]
name = "my-project"
version = "1.3.0"In setup.py (Python)
setup(
name="my-project",
version="1.3.0",
...
)In Git Tags
Tag releases so people can reference specific versions:
git tag -a v1.3.0 -m "Release version 1.3.0"
git push origin v1.3.0Note: The v prefix is convention, not part of SemVer. The actual version is 1.3.0, but tagging as v1.3.0 is common practice.
In Documentation
Always document version changes in a CHANGELOG.md:
# Changelog
## [2.0.0] - 2024-12-20
### Breaking Changes
- Changed `calculatePrice()` to require `taxRate` parameter
### Added
- New `formatCurrency()` helper function
### Fixed
- Fixed crash when processing empty orders
## [1.3.0] - 2024-12-15
### Added
- Added `exportToCSV()` function
...Use the Keep a Changelog format.
Common Questions and Mistakes
“Should I bump MAJOR for internal refactoring?”
No. If you don’t change the public API (the functions, methods, and behavior users depend on), it’s a PATCH or MINOR change depending on whether you added features.
Internal refactoring that doesn’t change behavior = PATCH Internal improvements that add capability = MINOR
“I accidentally released a breaking change as MINOR. What now?”
- Acknowledge the mistake
- Release a new MINOR version that restores backwards compatibility
- Document the broken version so users avoid it
- Consider releasing a new MAJOR version with the breaking change properly labeled
Example:
1.3.0- Accidentally broke things1.3.1- Reverted breaking change, restoring compatibility2.0.0- Properly implemented breaking change
“What if I need to fix a bug in an old MAJOR version?”
Branch from that version and release a PATCH:
# If current is 2.x.x but you need to fix 1.x.x
git checkout v1.5.0
git checkout -b v1-bugfix
# Fix the bug
git tag v1.5.1“Can I skip versions?”
Technically yes, but don’t. Version numbers should increase sequentially. Skipping versions confuses users.
Bad: 1.2.0 → 1.5.0 (where did 1.3 and 1.4 go?)
Good: 1.2.0 → 1.3.0 → 1.4.0 → 1.5.0
“What about marketing versions like ‘Windows 11’ or ‘macOS Ventura’?”
SemVer is for technical versioning. If you want marketing names, use both:
User-facing: “Project Phoenix”
Technical: v2.3.1
Document both in your releases.
Dependency Management with SemVer
When specifying dependencies, use version ranges:
NPM (package.json)
{
"dependencies": {
"express": "^4.18.0", // 4.18.0 ≤ version < 5.0.0
"lodash": "~4.17.21", // 4.17.21 ≤ version < 4.18.0
"react": "18.2.0" // Exact version
}
}Common patterns:
^1.2.3- Compatible with 1.2.3 (allows MINOR and PATCH updates)~1.2.3- Approximately 1.2.3 (allows PATCH updates only)1.2.3- Exact version>=1.2.3 <2.0.0- Explicit range
Cargo (Rust)
[dependencies]
serde = "1.0" # ^1.0.0 (MINOR and PATCH)
tokio = "~1.35" # ~1.35.0 (PATCH only)
reqwest = "=0.11.24" # Exact versionPython (requirements.txt)
Django>=3.2,<4.0
requests~=2.28.0
pytest==7.4.0Why this matters: If everyone uses SemVer correctly, you can specify dependencies like ^1.2.0 and trust that updates won’t break your code until a MAJOR version change.
Versioning Checklist
Before releasing a new version:
- Determine the type of changes (MAJOR, MINOR, or PATCH)
- Update version number in project files
- Update CHANGELOG.md with changes
- Tag the release in Git
- Write clear release notes explaining changes
- If MAJOR: Provide migration guide for breaking changes
- If deprecating: Give users at least one MINOR release warning
Why This Matters for SEAD Club
Consistent versioning across our projects means:
- Contributors know what changes are appropriate for a given release
- Users can trust updates based on version numbers
- We communicate professionally about our software
- Dependencies between projects are manageable
When everyone follows SemVer, we all benefit from predictable, trustworthy version numbers.
Further Reading
- Official Spec: semver.org
- Changelog Format: keepachangelog.com
- Git Tagging: See our Project Guidelines
Contributing to These Conventions
Have questions about versioning a specific change? Not sure if something counts as breaking? Ask in our community channels or open an issue for discussion.
These conventions work best when we all understand and apply them consistently. Your questions help us clarify and improve these guidelines for everyone.