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, then10 > 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.
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_versionsThe key returns integer tuples; sorted stays stable so equal versions keep their input order.
Common follow-up questions
- What if versions have different numbers of components, like `'1.0'` versus `'1.0.0'`? (Tests whether they pad the shorter tuple with zeros or rely on tuple comparison treating the shorter one as smaller.)
- What changes if a component can carry a suffix like `'1.2.0-rc1'`? (Tests splitting the numeric core from the pre-release tag and ordering releases after their release candidates.)
- How would you produce this ordering in SQL instead of Python? (Tests reaching for `SPLIT_PART` plus `CAST(... AS INTEGER)` per component rather than an ORDER BY on the raw text.)