The Version Ranker

A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.

Domain
Python
Difficulty
medium
Seniority
L3
Asked in
TetraScience

Problem

Our release dashboard keeps showing '3.0.10' as older than '3.0.9', because it orders the version strings as plain text. Return versions ordered from the oldest release to the newest; every version in one list has the same number of dot-separated parts.

Software versions follow their own ordering rules.

Example

Input:

versions = ["1.2.10","1.10.0","1.2.9","2.0.0"]

Output:

["1.2.9","1.2.10","1.10.0","2.0.0"]

Worked solution and explanation

The trap hiding in a plain sort

This is numeric comparison wearing a string costume. Every version is text, so the reflex is sorted(versions), and it even looks right on toy inputs. The real skill being probed: can you sort by a computed key that reflects what the data actually means, not by the bytes it happens to be stored as? A plain string sort compares character by character, so it reads '1.2.10' and '1.2.9' one glyph at a time, hits '1' versus '9' in the third component, and declares '10' the older release. Get this wrong and your 'latest version' selector silently ships the wrong build the moment any component crosses a digit boundary.

Why a plain sort fails

Lexicographic (wrong)
sorted(['1.2.10', '1.2.9']) compares '1.2.10' and '1.2.9' character by character. Both start '1.2.', then it hits '1' vs '9' and, since '1' < '9', ranks '1.2.10' first. The output puts version 10 before version 9.
Numeric key (right)
sorted(..., key=parse_version) compares (1, 2, 10) and (1, 2, 9). First two elements tie, then 10 > 9, so '1.2.9' sorts first. The tuple carries the real ordering; the strings ride along untouched.

Building the key

Step 1: Split each string into its components

v.split('.') turns '1.2.10' into ['1', '2', '10']. These are still strings, which is precisely the state that causes the bug if you compare them now, so this step is only half the work.

Step 2: Cast every component to an integer

Convert each part with int(p) so '10' becomes the number 10. This is the load-bearing line: it is what makes 10 > 9 instead of '10' < '9'. Skip it and you have written a slower version of the broken lexicographic sort.

Step 3: Return a tuple so comparison is element-by-element

Pack the integers into a tuple. Tuples compare lexicographically over their elements, so (1, 2, 10) vs (1, 2, 9) resolves component by component exactly like a human reads a version number. Hand this key to sorted and it keeps the original strings while ordering by the numbers.

Integer tuple sort key for version strings
def sort_versions(versions: list) -> list:
    def parse_version(v):
        parts = v.split(".")
        result = []
        for p in parts:
            result.append(int(p))
        return tuple(result)
    sorted_versions = sorted(versions, key=parse_version)
    return sorted_versions

The key returns integer tuples; sorted stays stable so equal versions keep their input order.

Common follow-up questions