Python
version comparison
programming
software development
tutorials

How do I compare version numbers in Python?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Version strings look simple until plain string comparison gives the wrong answer. For example, 2.10 is newer than 2.9, but lexical comparison treats those values as text rather than as version components.

In Python, the reliable approach is to parse versions into version-aware objects and compare those objects. If you are working with Python package versions, the packaging library is the practical default because it understands release candidates, post releases, and other real-world version forms.

Use packaging.version.Version

For Python-style package versions, packaging.version.Version is the right tool:

python
1from packaging.version import Version
2
3v1 = Version("2.10.0")
4v2 = Version("2.9.5")
5
6print(v1 > v2)
7print(Version("1.0rc1") < Version("1.0"))

This is better than custom parsing because it already knows how to order pre-releases, final releases, and post releases correctly.

It also makes the intent obvious. Anyone reading the code can see that these values are versions, not arbitrary strings.

Why Raw String Comparison Fails

Plain string comparison is character-based:

python
print("2.10" > "2.9")

That result is not trustworthy for versioning. It compares the characters from left to right rather than comparing numeric segments and release semantics.

Even a hand-rolled tuple parser becomes fragile once suffixes appear. A format that starts as major.minor.patch often grows into 1.0rc1, 1.0.post1, or 2.0.dev0. At that point, the simple parser has to become a version parser, and you are reimplementing logic that already exists.

Sort and Filter Versions Correctly

Once versions are parsed, sorting is straightforward:

python
1from packaging.version import Version
2
3raw_versions = ["1.0", "1.0rc1", "1.0.post1", "0.9.9", "2.0b1"]
4sorted_versions = sorted(raw_versions, key=Version)
5
6print(sorted_versions)

Range checks are also common. For those, SpecifierSet is useful:

python
1from packaging.specifiers import SpecifierSet
2from packaging.version import Version
3
4constraint = SpecifierSet(">=1.4,<2.0")
5
6for text in ["1.3.9", "1.4.2", "2.0"]:
7    version = Version(text)
8    print(text, version in constraint)

This is especially helpful in deployment tooling, compatibility validation, and feature gating.

Wrap the Logic in a Small Helper

If your code compares versions in several places, a helper keeps error handling in one place:

python
1from packaging.version import Version, InvalidVersion
2
3
4def is_upgrade(current: str, candidate: str) -> bool:
5    try:
6        return Version(candidate) > Version(current)
7    except InvalidVersion as exc:
8        raise ValueError(f"invalid version: {exc}") from exc
9
10
11print(is_upgrade("1.4.9", "1.5.0"))
12print(is_upgrade("1.5.0", "1.5.0"))

This is clearer than scattering Version(...) calls and exception handling through unrelated business code.

When a Simple Tuple Is Good Enough

Sometimes you control the format completely and know the values are always numeric segments with no suffixes. In that narrow case, a tuple comparison is acceptable:

python
1def simple_key(text: str):
2    return tuple(int(part) for part in text.split("."))
3
4
5print(simple_key("2.10") > simple_key("2.9"))

This is fine for tightly controlled internal data. It is not a good general solution for package versions, external APIs, or any version scheme that may evolve.

Common Pitfalls

The most common mistake is comparing raw strings directly. That works only when the values happen to line up with lexical ordering.

Another common issue is writing a custom parser too early. It may look shorter on day one, but it becomes technical debt as soon as version suffixes or ecosystem-specific rules appear.

Developers also assume every version scheme follows the same rules. Python package versions, Docker tags, mobile app versions, and vendor-specific build numbers may all have different semantics.

Finally, do not silently ignore invalid version strings. If an input is malformed, fail clearly and early instead of treating it as a zero or leaving it to string comparison by accident.

Summary

  • Parse version strings before comparing them.
  • Use packaging.version.Version for Python package-style versions.
  • Use SpecifierSet for range checks such as compatibility constraints.
  • A tuple parser is acceptable only for very simple, fixed numeric formats.
  • Avoid raw string comparison unless you want incorrect results.

Course illustration
Course illustration

All Rights Reserved.