Semantic Versioning
What the numbers in version 2.4.1 mean: MAJOR.MINOR.PATCH, dependency ranges and the link to git tags.
Why versions need rules
The numbers in a version like 2.4.1 carry meaning. When you have a dozen dependencies and each one
updates in its own way, you need a shared language: "this version is compatible with the old one" or
"this one will break the API". The agreement on how to read these numbers is called Semantic
Versioning (SemVer). The full rules are at semver.org.
Without rules, the jump 1.0 → 1.1 could mean anything: a minor fix or a full rewrite. SemVer removes
the guesswork.
MAJOR.MINOR.PATCH
A version is three numbers separated by dots:
2 . 4 . 1
MAJOR MINOR PATCH
- PATCH (
2.4.1→2.4.2): bug fixes, same API. Safe to update. - MINOR (
2.4.1→2.5.0): new functionality, old behavior keeps working. Backward compatibility is kept. - MAJOR (
2.4.1→3.0.0): incompatible changes. Something in the API was removed or changed, old code may break.
The rule is simple: break compatibility, bump MAJOR. Add something without breaking, bump MINOR. Fix something, bump PATCH.
When you bump a number, every number to its right resets to zero. After 2.7.4, a new feature gives
2.8.0, not 2.8.4. A breaking change after 2.7.4 gives 3.0.0. If a release has both fixes and a new
feature, the higher level applies: that's MINOR.
Numbers compare as numbers, not as strings. 2.10.0 is newer than 2.9.0, even though a text comparison
puts 2.10 before 2.9. Leading zeros are forbidden: 01.2.3 isn't a SemVer version.
Pre-releases and build metadata
Before the first stable release, a version often starts at 0.x. That means "the API isn't stable yet,
it can change at any moment".
Pre-releases are marked with a dash suffix: 1.0.0-rc.1, 2.0.0-beta. A pre-release is older than its
release: 1.0.0-rc.1 comes before 1.0.0. Numeric parts of the suffix compare as numbers, so the order
is:
1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0-rc.2 < 1.0.0-rc.10 < 1.0.0
After a + sign comes build metadata: 1.0.0+build.5. It doesn't affect version order, 1.0.0+build.5
and 1.0.0 are equal for comparison.
Ranges in dependencies
SemVer exists so package managers can update dependencies safely. In package.json you'll see entries
like:
"express": "^4.18.2",
"lodash": "~4.17.21"
^(caret) allows updates that don't change MAJOR:^4.18.2will take4.19.0and4.18.5, but not5.0.0.~(tilde) on a fullX.Y.Zversion only allows PATCH:~4.17.21will take4.17.25, but not4.18.0. If MINOR isn't specified, the tilde is wider:~1means>=1.0.0 <2.0.0.- an exact version (
4.18.2) pins exactly that one.
Without a lock file, npm install picks the newest published version that falls within the range.
The logic relies on SemVer's promise: nothing breaks within the same MAJOR, which is why updating with ^
is relatively safe. If a package doesn't honestly follow SemVer, the promise is worthless. That's why lock
files exist (package-lock.json in npm): they record the exact versions that were installed, and the
next install from the lock file gives the same dependency tree.
Pre-releases don't fall into a normal range. ^1.0.0 won't take 2.0.0-beta, or even 1.5.0-rc.1: npm
only lets a pre-release in when the range itself is written with a pre-release of the same version, for
example ^1.5.0-rc.0.
Special case: 0.x versions
The rule "caret keeps MAJOR fixed" stops working when MAJOR is zero. For zero versions, SemVer makes no
guarantees: 0.x counts as a period of active development, and a breaking change there is usually
shipped in MINOR.
Package managers account for this and narrow the range:
^1.2.3 → >=1.2.3 <2.0.0 any 1.x no lower than 1.2.3
^0.2.3 → >=0.2.3 <0.3.0 won't go up to 0.3.0
^0.0.3 → >=0.0.3 <0.0.4 that is, only this version
The mistake here is expensive precisely because intuition says the opposite. A person reads ^0.2.3,
thinks "it'll update within zero, it'll reach 0.9", installs the dependency, and expects fresh versions. But
npm honestly keeps it on the 0.2.x branch and won't pull in even a critical fix released in 0.3.1.
Practical takeaway: watch a 0.x dependency by hand. Automation won't bring its updates.
Git tags and releases
A version is pinned in git with a tag. A tag is a named marker on a specific commit. By convention a
version tag starts with v:
git tag -a v2.4.1 -m "release 2.4.1"
git push origin v2.4.1
The version itself is 2.4.1, the v is only a prefix on the tag name. The FAQ on semver.org says
outright that v1.2.3 isn't a semantic version, but the v prefix is accepted everywhere.
The -a flag creates an annotated tag: besides pointing at a commit, it carries an author, a date, and a
message. Releases use this kind of tag. We'll cover tag types in detail in the next lesson.
The tag v2.4.1 points at the commit this release was built from, and after publishing it stays put. Git
enforces this: if a tag with that name already exists in the remote repository, a plain push of another
tag with the same name is rejected. CI is usually set up so that pushing a tag triggers a build and
publishes an artifact for that version. That ties the version number, the commit, and the built image
tightly together.
Common mistakes
- Didn't reset the lower numbers. After
2.7.4, a new feature gives2.8.0. Version2.8.4is technically valid, but a reader will assume there were four fixes between2.8.0and2.8.4. - Picked the wrong last release.
git tagsorts names as strings, sov2.9.0ends up belowv2.10.0in the list. Sort by version instead:git tag --sort=-v:refname. - Forgot to push the tag.
git pushwithout a tag name only sends branches, and tags stay on your machine. Push a tag separately:git push origin v2.4.1. - Removed a feature in MINOR. Removing a flag, a field in the API response, or a config parameter breaks whoever relied on it. That's MAJOR, even if the change itself is small.
- Waiting for updates from
^0.x. The range^0.2.3won't go past0.2.x, see the section above.
What you'll need for the tasks
- the rules for bumping MAJOR, MINOR, and PATCH, and resetting the lower numbers;
- how
^and~turn into a range, including cases with a zero MAJOR and pre-releases; git tagandgit tag --sort=-v:refname,git log --oneline --decorate;git tag -a <tag> -m "<message>",git push origin <tag>;- the
-C <folder>flag: run a Git command on a repository in another folder.
In the next lesson we'll cover tags and releases in git in more detail, and how release pages get built from them.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson git-workflow-01-versioning-semver
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress