Skip to the code
cf.

Array.prototype.sort

MediumPolyfills

Write Array.prototype.mySort(compareFn), which works like the built-in sort without calling it (or toSorted). It sorts the array in place and returns that same array.

With compareFn, compareFn(a, b) decides the order of two elements: a negative result puts a first, a positive one puts b first, and 0 (or NaN) means they're equal.

Without compareFn, elements are converted to strings and compared the way < compares strings, by UTF-16 code units. So 10 comes before 9, and "B" before "a".

The sort is stable: elements that are equal keep the order they had.

undefined and holes are never passed to compareFn. After the sort, the other elements come first, sorted, then every undefined, then every hole. The length doesn't change.

If compareFn is given (not undefined) but isn't a function, throw a TypeError before changing anything.

const numbers = [10, 9, 1];
numbers.mySort();                // [1, 10, 9]: compared as strings
numbers.mySort((a, b) => a - b); // [1, 9, 10]
 
const people = [
  { name: "Ada", age: 30 },
  { name: "Bo", age: 25 },
  { name: "Cy", age: 30 },
];
people.mySort((a, b) => a.age - b.age).map((person) => person.name);
// ["Bo", "Ada", "Cy"]: Ada stays ahead of Cy
 
[3, undefined, , 1].mySort((a, b) => a - b);
// [1, 3, undefined, empty]

What the tests check

  1. sorts in place and returns the same array
    const numbers = [3, 1, 2];
    const result = numbers.mySort((a, b) => a - b);
    expect(numbers).toEqual([1, 2, 3]);
    expect(result).toBe(numbers);
    
  2. orders elements by compareFn's result
    expect([3, 1, 2].mySort((a, b) => b - a)).toEqual([3, 2, 1]);
    expect(["ccc", "a", "bb"].mySort((a, b) => a.length - b.length)).toEqual(["a", "bb", "ccc"]);
    expect([5, 3, 8, 1, 9, 2, 7, 4, 6, 0].mySort((a, b) => a - b)).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    
  3. compares as strings, by code unit, without compareFn
    expect([10, 9, 1, 100].mySort()).toEqual([1, 10, 100, 9]);
    expect(["b", "a", "B", "A"].mySort()).toEqual(["A", "B", "a", "b"]);
    expect(["é", "z", "e"].mySort()).toEqual(["e", "z", "é"]);
    expect([true, null, false].mySort()).toEqual([false, null, true]);
    
  4. is stable: equal elements keep their order
    const people = [
      { name: "Ada", age: 30 },
      { name: "Bo", age: 25 },
      { name: "Cy", age: 30 },
      { name: "Di", age: 25 },
      { name: "Ed", age: 30 },
    ];
    people.mySort((a, b) => a.age - b.age);
    expect(people.map((person) => person.name)).toEqual(["Bo", "Di", "Ada", "Cy", "Ed"]);
    const items = Array.from({ length: 40 }, (_, i) => ({ group: (i * 7) % 3, i }));
    const expected = [0, 1, 2].flatMap((group) => items.filter((item) => item.group === group));
    items.mySort((a, b) => a.group - b.group);
    expect(items).toEqual(expected);
    
  5. keeps the order when compareFn returns 0 or NaN
    expect(["b", "c", "a"].mySort(() => 0)).toEqual(["b", "c", "a"]);
    expect([3, 1, 2, 5, 4].mySort(() => NaN)).toEqual([3, 1, 2, 5, 4]);
    
  6. puts undefined after everything else, without passing it to compareFn
    const compareFn = spy((a, b) => a - b);
    const values = [3, undefined, 1, undefined, 2];
    values.mySort(compareFn);
    expect(values).toEqual([1, 2, 3, undefined, undefined]);
    expect(compareFn.calls.flatMap((call) => call.args)).not.toContain(undefined);
    expect(["b", undefined, "a", "undefined"].mySort()).toEqual(["a", "b", "undefined", undefined]);
    
  7. moves holes to the very end and keeps them as holes
    const values = [3, , undefined, 1, ,];
    values.mySort((a, b) => a - b);
    expect(values).toHaveLength(5);
    expect(values).toEqual([1, 3, undefined, , ,]);
    expect(2 in values).toBe(true);
    expect(3 in values).toBe(false);
    expect(4 in values).toBe(false);
    expect([, "b", "a"].mySort()).toEqual(["a", "b", ,]);
    
  8. throws a TypeError for a compareFn that isn't a function, leaving the array alone
    const values = [2, 1];
    expect(() => values.mySort("desc")).toThrow(TypeError);
    expect(() => values.mySort(null)).toThrow(TypeError);
    expect(() => values.mySort({})).toThrow(TypeError);
    expect(values).toEqual([2, 1]);
    expect(values.mySort(undefined)).toEqual([1, 2]);
    
  9. works without the built-in sort or toSorted
    const builtIn = { sort: Array.prototype.sort, toSorted: Array.prototype.toSorted };
    Array.prototype.sort = function () {
      throw new Error("mySort called the built-in sort");
    };
    Array.prototype.toSorted = function () {
      throw new Error("mySort called the built-in toSorted");
    };
    let result;
    try {
      result = [3, 1, 2].mySort((a, b) => a - b);
    } finally {
      Array.prototype.sort = builtIn.sort;
      Array.prototype.toSorted = builtIn.toSorted;
    }
    expect(result).toEqual([1, 2, 3]);
    

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).