Swift
Version Comparison
String Manipulation
Programming
Code Snippet

Compare two version strings in Swift

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 like ordinary strings, but naive lexicographic comparison gives the wrong result for values such as 1.2.10 and 1.2.9. In Swift, the safest solution is to compare version components numerically, either with a custom parser or, for simpler cases, with string comparison using the .numeric option.

Why Plain String Comparison Fails

A regular string comparison is character-based, not version-aware.

swift
1let a = "1.2.10"
2let b = "1.2.9"
3
4print(a < b)

That can produce the wrong ordering because the comparison is driven by characters rather than by numeric segments.

Version strings need to be interpreted as sequences of integers separated by dots.

A Robust Component-by-Component Comparison

A reliable approach is to split each version string by . and compare the integer parts one by one.

swift
1import Foundation
2
3enum VersionOrder {
4    case ascending
5    case same
6    case descending
7}
8
9func compareVersions(_ lhs: String, _ rhs: String) -> VersionOrder {
10    let lhsParts = lhs.split(separator: ".").map { Int($0) ?? 0 }
11    let rhsParts = rhs.split(separator: ".").map { Int($0) ?? 0 }
12
13    let count = max(lhsParts.count, rhsParts.count)
14
15    for i in 0..<count {
16        let left = i < lhsParts.count ? lhsParts[i] : 0
17        let right = i < rhsParts.count ? rhsParts[i] : 0
18
19        if left < right { return .ascending }
20        if left > right { return .descending }
21    }
22
23    return .same
24}
25
26print(compareVersions("1.2.10", "1.2.9"))
27print(compareVersions("1.0", "1"))
28print(compareVersions("2.0", "2.0.0"))

This handles missing trailing components by treating them as zero, so 1, 1.0, and 1.0.0 compare as equal.

When .numeric Is Good Enough

Foundation also supports numeric-aware string comparison.

swift
1import Foundation
2
3let result = "1.2.10".compare("1.2.9", options: .numeric)
4print(result == .orderedDescending)

This is concise and works well for many simple version strings. But if you need precise control over semantics such as ignored suffixes, pre-release tags, or zero-padding rules, a custom parser is still better.

So a practical guideline is:

  • '.numeric for quick comparisons of straightforward dotted versions'
  • custom component parsing for precise application logic

Think About Pre-Release and Build Suffixes

Real version strings are often more complicated than major.minor.patch. You might see values like:

  • '1.2.0-beta'
  • '2.0.1+build7'
  • '3.1-rc1'

At that point, you need a policy. Do you support full semantic versioning or only numeric dotted releases?

If the app only stores numeric versions, keep the parser strict. If suffixes are expected, strip or interpret them deliberately instead of hoping string comparison will do the right thing.

A Comparable Version Type

If version comparison appears in many places, wrap it in a type instead of repeating ad hoc functions.

swift
1import Foundation
2
3struct Version: Comparable {
4    let parts: [Int]
5
6    init(_ raw: String) {
7        self.parts = raw.split(separator: ".").map { Int($0) ?? 0 }
8    }
9
10    static func < (lhs: Version, rhs: Version) -> Bool {
11        let count = max(lhs.parts.count, rhs.parts.count)
12
13        for i in 0..<count {
14            let left = i < lhs.parts.count ? lhs.parts[i] : 0
15            let right = i < rhs.parts.count ? rhs.parts[i] : 0
16            if left != right {
17                return left < right
18            }
19        }
20
21        return false
22    }
23}
24
25print(Version("1.2.3") < Version("1.10.0"))

This makes call sites cleaner and gives you one place to evolve the comparison rules later.

Common Pitfalls

  • Comparing version strings with ordinary < and > operators.
  • Forgetting to pad missing trailing components such as 1 versus 1.0.
  • Relying on .numeric when the versions contain suffixes that need real parsing logic.
  • Mixing semantic-version rules with simple dotted-number rules without defining the expected behavior.
  • Repeating custom comparison code in multiple places instead of centralizing it.

Summary

  • Version strings should usually be compared numerically by component, not lexicographically.
  • A custom split-and-compare function is the most reliable general solution in Swift.
  • '.compare(..., options: .numeric) is convenient for simpler dotted versions.'
  • Decide explicitly how to handle missing components and suffixes.
  • If version comparison is common in the codebase, wrap it in a dedicated type.

Course illustration
Course illustration

All Rights Reserved.