450

Let's suppose I wanted a sort function that returns a sorted copy of the inputted array. I naively tried this

function sort(arr) {
  return arr.sort();
}

and I tested it with this, which shows that my sort method is mutating the array.

var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a);  //alerts "1,2,3,3,3,4,5,7,7"

I also tried this approach

function sort(arr) {
  return Array.prototype.sort(arr);
}

but it doesn't work at all.

Is there a straightforward way around this, preferably a way that doesn't require hand-rolling my own sorting algorithm or copying every element of the array into a new one?

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Peter Olson
  • 139,199
  • 49
  • 202
  • 242
  • 1
    create a deep copy of the array and sort it instead. – evanmcdonnal Mar 06 '12 at 22:12
  • 2
    @evanmcdonnal A shallow copy might be good enough if all is wanted is a reordering and not a duplicate of every item in the array. – Kekoa Mar 06 '12 at 22:14
  • `.sort` requires the `this` value to be the array, so for the last snippet to work you would do `.sort.call(arr)` (though it doesn't solve your problem). – pimvdb Mar 06 '12 at 22:15
  • @Kekoa Yeah that's a good point. There is no need to consume more memory if you're only going to change the order of the elements and not the elements themselves. – evanmcdonnal Mar 06 '12 at 22:16
  • zzzzBov's method is working like a charm! https://stackoverflow.com/a/9592774/7011860 – zimmerbimmer Jun 27 '19 at 06:14

12 Answers12

482

You need to copy the array before you sort it. One way with es6:

const sorted = [...arr].sort();

The spread-syntax as array literal (copied from mdn):

var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Putzi San
  • 5,566
  • 3
  • 19
  • 35
  • this is really great.i think easier to understand than the concat and other approaches – sktguha Aug 31 '20 at 20:02
  • this code does work, but when I compile the JS with Gulp it comes back with an error, SyntaxError: Unexpected token: punc (.) – brassmookie Jun 10 '21 at 13:57
  • 6
    To those saying it's not valid JavaScript... it's perfectly valid. If you're in Chrome/Safari/Edge or Firefox: open the dev console, define an array called `arr` and paste the expression to see the result. – shangxiao Jun 17 '21 at 07:48
  • 3
    @Cerin It sounds like you are on an incredibly outdated version of JS. – Kloar Sep 10 '21 at 15:31
  • 1
    Is this method faster than `.slice()` to get a copy of the array? – S.Serpooshan Apr 28 '22 at 06:46
  • For more complex structures shallow cloning might be not enough, and something like this might be useful: `let arr2 = JSON.parse(JSON.stringify(arr));` – NiKO Oct 01 '22 at 22:02
  • @NiKO for sorting, it's ok to have the original object. If you mutate the object afterwards, then you might have issues – Garr Godfrey Feb 09 '23 at 05:05
258

Just copy the array. There are many ways to do that:

function sort(arr) {
  return arr.concat().sort();
}

// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • 4
    Will this do a deep copy, i.e., will nested objects and arrays also be copied? – Peter Olson Mar 06 '12 at 22:15
  • 3
    Is there any advantage to using `concat` over say `slice(0)` or are they all pretty much just the same? – JaredPar Mar 06 '12 at 22:15
  • @JaredPar The result is equal. If you're really after micro-performance, you can set up (or look up) a perf check at http://jsperf.com/ – Rob W Mar 06 '12 at 22:18
  • @RobW not really interested in the micro perf. All of the samples I'd seen before use `slice` and I wanted to see if there was some inherent reason to prefer `concat` – JaredPar Mar 06 '12 at 22:19
  • 7
    @PeterOlson No, it's a shallow copy. If you really want a deep copy, use the search feature on Stack Overflow to find existing excellent answers for that. – Rob W Mar 06 '12 at 22:19
  • 1
    @JaredPar JsPerf created. For my browse at least, concat was marginally faster. Link: http://jsperf.com/array-shallow-copying – starbeamrainbowlabs Dec 21 '13 at 18:19
  • slice is a bit faster for me (about 5% on firefox 45 and chrome 54 on linux). – Nico Toub Feb 23 '17 at 13:32
  • 18
    Slice is now reported as notably faster – Zander Brown May 31 '17 at 13:51
  • 1
    @PeterOlson and be *sure* to choose a solution which is in an npm package. Don't copy-paste some code. – masterxilo Oct 10 '18 at 12:45
  • 3
    As proposed a few answers below, you can also use `Array.from` which I find a little bit more expressive: `Array.from(arr).sort()` With `slice` and `concat`, I find the array duplication intension is less evident. – Stéphane May 20 '19 at 10:54
  • 7
    why `Array.prototype.slice.call(arr).sort();` instead of `arr.slice().sort();` ? – Olivier Boissé Oct 25 '19 at 19:08
  • 3
    @OlivierBoissé The prototype call works for array-like objects, too. Not only arrays themselves. – Scindix Apr 14 '20 at 23:36
80

Try the following

function sortCopy(arr) { 
  return arr.slice(0).sort();
}

The slice(0) expression creates a copy of the array starting at element 0.

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
55

You can use slice with no arguments to copy an array:

var foo,
    bar;
foo = [3,1,2];
bar = foo.slice().sort();
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
21

You can also do this

d = [20, 30, 10]
e = Array.from(d)
e.sort()

This way d will not get mutated.

function sorted(arr) {
  temp = Array.from(arr)
  return temp.sort()
}

//Use it like this
x = [20, 10, 100]
console.log(sorted(x))
Aditya Agarwal
  • 753
  • 6
  • 6
8

Update - Array.prototype.toSorted() proposal

The Array.prototype.toSorted(compareFn) -> Array is a new method that was proposed to be added to the Array.prototype and is currently in stage 3 (Soon to be available).

This method will keep the target Array untouched and return a copy with the change performed instead.

Ran Turner
  • 14,906
  • 5
  • 47
  • 53
3

ES2023 Array Method toSorted():

The toSorted() method of Array instances is the copying version of the sort() method. It returns a new array with the elements sorted in ascending order.

const arr = [2, 1, 3];
const arrSorted = arr.toSorted();
console.log(arr); //[2, 1, 3]
console.log(arrSorted); //[1, 2, 3]

PS: toSorted() method is supported nearly by all browsers and on Node.js version 20+.
see browser compatibility

XMehdi01
  • 5,538
  • 2
  • 10
  • 34
2

Anyone who wants to do a deep copy (e.g. if your array contains objects) can use:

let arrCopy = JSON.parse(JSON.stringify(arr))

Then you can sort arrCopy without changing arr.

arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)

Please note: this can be slow for very large arrays.

Hamada
  • 37
  • 1
  • 1
    This will work with `-` instead of `>` in your second example. – tkit May 15 '20 at 16:57
  • 2
    and remember all your items should be serializable in order to bring them back after stringifying ( eg. date objects, functions and symbols are problematic in this method ) – btargac Jan 20 '21 at 13:47
1

Try this to sort the numbers. This does not mutate the original array.

function sort(arr) {
  return arr.slice(0).sort((a,b) => a-b);
}
Prati
  • 9
  • 1
0

There's a new tc39 proposal, which adds a toSorted method to Array that returns a copy of the array and doesn't modify the original.

For example:

const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]

As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js.

Josh
  • 2,324
  • 1
  • 21
  • 29
0

You can also extend the existing Array functionality. This allows chaining different array functions together.

Array.prototype.sorted = function (compareFn) {
    const shallowCopy = this.slice();
    shallowCopy.sort(compareFn);

    return shallowCopy;
}

[1, 2, 3, 4, 5, 6]
    .filter(x => x % 2 == 0)
    .sorted((l, r) => r - l)
    .map(x => x * 2)

// -> [12, 8, 4]

Same in typescript:

// extensions.ts
Array.prototype.sorted = function (compareFn?: ((a: any, b: any) => number) | undefined) {
    const shallowCopy = this.slice();
    shallowCopy.sort(compareFn);

    return shallowCopy;
}

declare global {
    interface Array<T> {
        sorted(compareFn?: (a: T, b: T) => number): Array<T>;
    }
}

export {}

// index.ts
import 'extensions.ts';


[1, 2, 3, 4, 5, 6]
    .filter(x => x % 2 == 0)
    .sorted((l, r) => r - l)
    .map(x => x * 2)

// -> [12, 8, 4]
Niklas
  • 49
  • 3
0

To sort a function without mutating the original, simply use .map() before sort to create a copy of the original array:

const originalArr = [1, 45, 3, 21, 6];
const sortedArr = originalArr.map(value => JSON.parse(JSON.stringify(value))).sort((a, b) => a - b);

console.log(sortedArr); // the logged output will be 1,3,6,21,45

The original array has not been modified, but you have a sorted version of it available to you. JSON.parse(JSON.stringify()) make sure it is a deep copy, not a shallow copy.

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
Ville
  • 45
  • 6