Skip to the code
cf.

Compare versions

Write compareVersions(a, b). It returns -1 when version a is lower than b, 1 when it's higher, and 0 when they're equal, so it works as a sort comparator: versions.sort(compareVersions).

A version is numbers joined by dots, like 1.10.2, optionally followed by - and a pre-release tag, like 2.0.0-beta.3. The tag is everything after the first -.

Compare the numbers from left to right, as numbers rather than text: 1.10.0 is higher than 1.9.0. A missing number counts as 0, so 1.2 equals 1.2.0. The first difference decides.

Tags matter only when all the numbers are equal. Then a version without a tag is higher than one with a tag: 1.0.0-rc.1 comes before 1.0.0.

Two tags are compared identifier by identifier, splitting them at dots. Two identifiers made only of digits compare as numbers (beta.2 is lower than beta.10). Two other identifiers compare by character code, the way < compares strings (alpha is lower than beta, and Beta is lower than beta). A digits-only identifier is lower than any other (1.0.0-1 is lower than 1.0.0-alpha).

If every identifier matches but one tag has fewer, that tag is lower (alpha is lower than alpha.1).

compareVersions("1.10.0", "1.9.0");              // 1
compareVersions("1.2", "1.2.0");                 // 0
compareVersions("2.0.0-rc.1", "2.0.0");          // -1
compareVersions("1.0.0-beta.2", "1.0.0-beta.10"); // -1
 
["1.0.0", "1.0.0-rc.1", "0.9.12", "1.0.0-alpha.1", "1.0.0-alpha"].sort(compareVersions);
// ["0.9.12", "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-rc.1", "1.0.0"]

What the tests check

  1. returns 0 for equal versions
    expect(compareVersions("1.2.3", "1.2.3")).toBe(0);
    expect(compareVersions("0.0.0", "0.0.0")).toBe(0);
    expect(compareVersions("1.0.0-alpha.1", "1.0.0-alpha.1")).toBe(0);
    
  2. compares each part as a number, returning -1 or 1
    expect(compareVersions("1.10.0", "1.9.0")).toBe(1);
    expect(compareVersions("1.2.3", "1.2.10")).toBe(-1);
    expect(compareVersions("2.0.0", "10.0.0")).toBe(-1);
    expect(compareVersions("1.0.5", "1.0.2")).toBe(1);
    expect(compareVersions("3.0.0", "2.9.9")).toBe(1);
    
  3. counts missing parts as 0
    expect(compareVersions("1.2", "1.2.0")).toBe(0);
    expect(compareVersions("1", "1.0.1")).toBe(-1);
    expect(compareVersions("1.0.0.1", "1")).toBe(1);
    
  4. ranks a pre-release below its release
    expect(compareVersions("1.0.0-alpha", "1.0.0")).toBe(-1);
    expect(compareVersions("1.0.0", "1.0.0-rc.1")).toBe(1);
    expect(compareVersions("1.0.0-rc.1", "0.9.9")).toBe(1);
    
  5. compares digits-only identifiers as numbers
    expect(compareVersions("1.0.0-beta.2", "1.0.0-beta.10")).toBe(-1);
    expect(compareVersions("1.0.0-11", "1.0.0-9")).toBe(1);
    
  6. compares other identifiers by character code, after digits-only ones
    expect(compareVersions("1.0.0-alpha", "1.0.0-beta")).toBe(-1);
    expect(compareVersions("1.0.0-beta", "1.0.0-Beta")).toBe(1);
    expect(compareVersions("1.0.0-1", "1.0.0-alpha")).toBe(-1);
    expect(compareVersions("1.0.0-alpha.beta", "1.0.0-alpha.1")).toBe(1);
    
  7. ranks a shorter tag below a longer one it starts
    expect(compareVersions("1.0.0-alpha", "1.0.0-alpha.1")).toBe(-1);
    expect(compareVersions("1.0.0-alpha.1.2", "1.0.0-alpha.1")).toBe(1);
    
  8. sorts a list of versions in order
    const versions = ["1.0.0", "1.0.0-rc.1", "1.0.0-beta.11", "1.0.0-beta.2", "1.0.0-beta", "1.0.0-alpha.beta", "1.0.0-alpha.1", "1.0.0-alpha", "0.9.0"];
    expect([...versions].sort(compareVersions)).toEqual(["0.9.0", "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0"]);
    

Learn more

solution.jsSaved in this browser

Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.

Ctrl + Enter runs the tests (⌘ + Enter on a Mac).