68

I was asked recently what was the most efficient way to reverse an array in JavaScript. At the moment, I suggested using a for loop and fiddling with the array but then realized there is a native Array.reverse() method.

For curiosity's sake, can anyone help me explore this by showing examples or pointing in the right direction so I can read into this? Any suggestions regarding how to measure performance would be awesome too.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
luisgo
  • 2,385
  • 4
  • 24
  • 30
  • 1
    **Notice to future readers**: the majority of answers in this thread are inefficient (quadratic), unnecessarily difficult to read, buggy or flat-out incorrect. Use extreme caution when proceeding! I've protected the question (which is off-topic as too broad) to avoid additional low-quality posts. A lot of the performance conclusions and benchmarks are outdated or incorrect. Those with close/delete vote power, I strongly recommend purging this thread! – ggorlen Oct 04 '21 at 23:34

19 Answers19

83

Based on this setup:

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var length = array.length;

Array.reverse(); is the first or second slowest!

The benchmarks are here:

https://jsperf.com/js-array-reverse-vs-while-loop/9

Across browsers, swap loops are faster. There are two common types of swap algorithms (see Wikipedia), each with two variations.

The two types of swap algorithms are temporary swap and XOR swap.

The two variations handle index calculations differently. The first variation compares the current left index and the right index and then decrements the right index of the array. The second variation compares the current left index and the length divided by half and then recalculates the right index for each iteration.

You may or may not see huge differences between the two variations. For example, in Chrome 18, the first variations of the temporary swap and XOR swap are over 60% slower than the second variations, but in Opera 12, both variations of the temporary swap and XOR swap have similar performance.

Temporary swap:

First variation:

function temporarySwap(array)
{
    var left = null;
    var right = null;
    var length = array.length;
    for (left = 0, right = length - 1; left < right; left += 1, right -= 1)
    {
        var temporary = array[left];
        array[left] = array[right];
        array[right] = temporary;
    }
    return array;
}

Second variation:

function temporarySwapHalf(array)
{
    var left = null;
    var right = null;
    var length = array.length;
    for (left = 0; left < length / 2; left += 1)
    {
        right = length - 1 - left;
        var temporary = array[left];
        array[left] = array[right];
        array[right] = temporary;
    }
    return array;
}

XOR swap:

First variation:

function xorSwap(array)
{
    var i = null;
    var r = null;
    var length = array.length;
    for (i = 0, r = length - 1; i < r; i += 1, r -= 1)
    {
        var left = array[i];
        var right = array[r];
        left ^= right;
        right ^= left;
        left ^= right;
        array[i] = left;
        array[r] = right;
    }
    return array;
}

Second variation:

function xorSwapHalf(array)
{
    var i = null;
    var r = null;
    var length = array.length;
    for (i = 0; i < length / 2; i += 1)
    {
        r = length - 1 - i;
        var left = array[i];
        var right = array[r];
        left ^= right;
        right ^= left;
        left ^= right;
        array[i] = left;
        array[r] = right;
    }
    return array;
}

There is another swap method called destructuring assignment: http://wiki.ecmascript.org/doku.php?id=harmony:destructuring

Destructuring assignment:

First variation:

function destructuringSwap(array)
{
    var left = null;
    var right = null;
    var length = array.length;
    for (left = 0, right = length - 1; left < right; left += 1, right -= 1)
    {
        [array[left], array[right]] = [array[right], array[left]];
    }
    return array;
}

Second variation:

function destructuringSwapHalf(array)
{
    var left = null;
    var right = null;
    var length = array.length;
    for (left = 0; left < length / 2; left += 1)
    {
        right = length - 1 - left;
        [array[left], array[right]] = [array[right], array[left]];
    }
    return array;
}

Right now, an algorithm using destructuring assignment is the slowest of them all. It is even slower than Array.reverse();. However, the algorithms using destructuring assignments and Array.reverse(); methods are the shortest examples, and they look the cleanest. I hope their performance gets better in the future.


Another mention is that modern browsers are improving their performance of array push and splice operations.

In Firefox 10, this for loop algorithm using array push and splice rivals the temporary swap and XOR swap loop algorithms.

for (length -= 2; length > -1; length -= 1)
{
    array.push(array[length]);
    array.splice(length, 1);
}

However, you should probably stick with the swap loop algorithms until many of the other browsers match or exceed their array push and splice performance.

Vibhor
  • 70
  • 9
XP1
  • 6,910
  • 8
  • 54
  • 61
  • 9
    I think it should be noted all the functions above, except for the temporary swap, only work on integer arrays and not arrays with other types (strings, objects, etc). – DisgruntledGoat Feb 24 '13 at 22:22
  • 8
    Your benchmark's "for push then slice" test clobbers the global `length`, making all the tests after it meaningless. – Boris Zbarsky Mar 16 '13 at 21:05
  • 11
    Boris is right: http://jsperf.com/js-array-reverse-vs-while-loop/9. Furthermore, in Google Chrome array.reverse is faster than the other methods – Ivan Castellanos Mar 21 '13 at 09:12
  • 1
    Yes, in Google Chrome in 2016, array.reverse is at least twice faster than anything else. See the jsperf link in the other comment. – Max Jan 04 '17 at 00:28
  • These code suggestions are poor quality (written like ANSI C) and the benchmark is incorrect. Don't prematurely optimize, use the builtin `array.reverse()` if you want in-place, or `array.slice().reverse()` if you want a copy. Standard libs are already likely to be fastest, are undoubtedly cleanest to understand, and their speed increases for free as they're optimized over time. – ggorlen Oct 04 '21 at 23:39
19

In simple way you can do this using map.

let list = [10, 20, 30, 60, 90]
let reversedList = list.map((e, i, a)=> a[(a.length -1) -i]) // [90, 60...]
Srinivas Damam
  • 2,893
  • 2
  • 17
  • 26
18

Native methods are always faster.

So use Array.reverse where possible. Otherwise an implementation that runs in O(1) would be best ;)

Otherwise just use something like this

var reverse = function(arr) {
   var result = [],
       ii = arr.length;
   for (var i = ii - 1;i !== 0;i--) {
       result.push(arr[i]);
   }
   return result;
}

Benchmark!

Interesting the loop is faster if you use all three stages of the for construct instead of only one.

for(var i = ii - 1; i !== 0;i--) is faster then var i = ii - 1;for(;i-- !== 0;)

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • 2
    @Matt McDonald Interesting, we just did a project in college proving otherwise lol. Although we tested all sorts of functionality like bubble-sort, heap-sort, quick-sort. The timing on stuff like this has a lot to do with how the array is already layed out. (Is it already sorted, is it already random?) Different types of sorts / reverse sorts often depend on the initial state of the array. It is hard to PROVE which is fastest as their are many many different scenarios. – clamchoda Mar 16 '11 at 21:46
  • Improve your algorithm, and try again. Now, `Array.reverse();` is the first or second slowest! – XP1 Feb 02 '12 at 13:29
  • 4
    In Javascript, native methods are almost always slower because the design of the language is [fundamentally broken](http://javascript-puzzlers.herokuapp.com/). See [bind vs. closure](http://stackoverflow.com/questions/17638305); [for loop versus map](http://stackoverflow.com/questions/6551139); [for versus .forEach](https://coderwall.com/p/kvzbpa). This is because the builtins cannot make the assumptions about the array that you can, and so must perform significantly more tests. If you are concerned about performance, consider doing your own loops or lodash; use native methods for readability. – Brian M. Hunt Feb 23 '14 at 21:07
  • 5
    @Raynos Your example is broken; it doesn't iterate the entire array. It should read `var i = ii - 1;i >= 0;i--`. – mcNux May 16 '14 at 12:41
  • `for (var i = ii - 1;i !== 0;i--) {` gives an infinite loop when the array is empty. `ii = arr.length;` is a pointless variable. This is sloppy code. Avoid. "Otherwise an implementation that runs in O(1) would be best ;)" is impossible and confusing, I assume it's a joke due to the winky but why? "`for(var i = ii - 1; i !== 0;i--)` is faster then `var i = ii - 1;for(;i-- !== 0;)`" is just bad benchmarking -- whether the initialization and decrement is inside the loop initializer or outside of it doesn't matter at all. – ggorlen Oct 04 '21 at 18:19
11

I opened a Firefox bug about slow reverse performance in Firefox. Someone from Mozilla looked at the benchmark used in the accepted post, and says that it is pretty misleading -- in their analysis the native method is better in general for reversing arrays. (As it should be!)

starwed
  • 2,536
  • 2
  • 25
  • 39
  • 2
    People need to read through the whole bug case if they're interested in this topic. There seems to be more to it since you posted your answer, but I think for sure I'd stick with native `Array.reverse()`. – CWSpear Jul 20 '13 at 05:59
4

This is the most efficient and clean way to reverse an array with the ternary operator.

function reverse(arr) {
  return arr.length < 2 ? arr : [arr.pop()].concat(reverse(arr));
}
console.log(reverse([4, 3, 3, 1]));
Arslan shakoor
  • 1,387
  • 1
  • 9
  • 7
  • 2
    This isn't efficient at all -- it's quadratic (calling `concat` in a loop, basically re-allocating and building the entire result array for every recursive call) and blows the stack if the array is too large due to recursion (try it with `reverse(Array(10000).fill(0))`). – ggorlen Oct 04 '21 at 23:15
3

Swap functions are the fastest. Here's a reverse function I wrote that is only slightly similar to the swap functions mentioned above but performs faster.

function reverse(array) {
  var first = null;
  var last = null;
  var tmp = null;
  var length = array.length;

  for (first = 0, last = length - 1; first < length / 2; first++, last--) {
    tmp = array[first];
    array[first] = array[last];
    array[last] = tmp;
  }
}

You can find the benchmarking here http://jsperf.com/js-array-reverse-vs-while-loop/19

Brice Lin
  • 532
  • 5
  • 11
3

Since no one came up with it and to complete the list of ways to reverse an array...

array.sort(function() {
    return 1;
})

It's twice as fast as both while-approaches, but other than that, horribly slow.

http://jsperf.com/js-array-reverse-vs-while-loop/53

CodeManX
  • 11,159
  • 5
  • 49
  • 70
  • 3
    It won't reverse the array; maybe it used to do it, but it's implementation dependent. Right now I tested it in the newest stable V8 and it doesn't reverse. In fact, it the order remained the same. However, it reversed it, when I returned -1. It might be because in ES2019 the .sort method is required to perform stable sorting. But still, I wouldn't rely on that. – pepkin88 Nov 29 '19 at 10:31
  • 1
    Interesting. It probably depends on the algorithm used for sorting, so yeah it is essentially implementation-specific. As long as every element is only visited once, -1 should reverse the order and 1 keep the current order. Otherwise it will be arbitrary. – CodeManX Dec 03 '19 at 12:12
  • Sorting is O(n log(n)) but reverse is O(n). This doesn't scale and seems to be implementation-dependent as mentioned above. Fails for me in Chrome 94. Even if it did work and was efficient, this is at best totally confusing and unsemantic: how does `sort(() => 1)` communicate "reverses the array" to any reader of the code? – ggorlen Oct 04 '21 at 17:15
3

You can do this with .slice().reverse():

const yourArray = ["first", "second", "third", "...", "etc"];
const reversedArray = yourArray.slice().reverse();
console.log(reversedArray);

Note that .slice() is used to prevent modification of yourArray since .reverse() is in-place.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
1

Here is another example to permanently modify the array reversing it's elements:

var theArray = ['a', 'b', 'c', 'd', 'e', 'f'];

function reverseArrayInPlace(array) {
  for (var i = array.length - 1; i >= 0; i -= 1) {
    array.push(array[i]);
  }
  array.splice(0, array.length / 2);
  return array;
};
reverseArrayInPlace(theArray);
console.log(theArray); // -> ["f", "e", "d", "c", "b", "a"]
drjorgepolanco
  • 7,479
  • 5
  • 46
  • 47
1

Another suggestion, similar to the above, but using splice instead:

var myArray=["one","two","three","four","five","six"];
console.log(myArray);
for(i=0;i<myArray.length;i++){
myArray.splice(i,0,myArray.pop(myArray[myArray.length-1]));
}
console.log(myArray);
  • [`.pop()` doesn't take any parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop). Also, `for(i=0` declares a global, possibly creating a bug. Please use `var`/`let`/`const`. – ggorlen Oct 06 '21 at 02:19
1

Here's a java example http://www.leepoint.net/notes-java/data/arrays/arrays-ex-reverse.html showing how to reverse an array. Very easy to convert to javascript.

I would suggest using something that simply captures the time before the function is called, and after the function is called. Which ever takes the least time / clock cycles will be the fastest.

clamchoda
  • 4,411
  • 2
  • 36
  • 74
0

If you want to copy a reversed version of an array and keep the original as it is:

a = [0,1,2,3,4,5,6,7,8,9];
b = []
for(i=0;i<a.length;i++){
    b.push(a.slice(a.length-i-1,a.length-i)[0])
}

Output of b:

[ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
tarikakyol
  • 513
  • 7
  • 13
  • 5
    Or just `b = a.slice().reverse()` :) – nishanthshanmugham Aug 06 '15 at 01:40
  • 1
    Or just `b = [...a].reverse()` :) – Oliboy50 Apr 18 '20 at 09:00
  • `a.slice(a.length-i-1,a.length-i)[0]` is really strange way to index into a single element of an array. Don't use this. – ggorlen Oct 04 '21 at 17:22
  • Also, always use `var`, `let` or `const` instead of declaring globals. `for(i=0;...` should never be done. – ggorlen Oct 04 '21 at 23:27
  • @ggorlen, instead of editing the content and adding the change, prefer to vote as unhelpful answer and comment on the correction he could have made. – Luis Lobo Oct 05 '21 at 17:45
  • @LuisLobo no, I'm not going to change OP's intent. That's not what edits are for. The code is basically unsalvageable, along with most of the other code in this thread, and future visitors need to be warned about it lest they use it in production and wind up with serious bugs and wasted time/money. After "fixing" the code (it'd still be very poor-quality), it'd essentially be [this](https://stackoverflow.com/a/62234235/6243352). – ggorlen Oct 05 '21 at 18:43
  • @ggorlen Any older answer is already a point of attention. It may not be the best, it may be that for some reason it gives a problem, but that doesn't mean that it doesn't work and, mainly, that whoever shows up here might not want to test it. – Luis Lobo Oct 06 '21 at 11:39
  • @LuisLobo What's your point, exactly? I don't understand your agenda in defending poor-quality answers or chiding someone for downvoting. If you like the answer for whatever reason, you're free to upvote it, just as I'm free to vote as I please. The tooltip on the downvote button reads: "This answer is not useful". In my opinion, this answer is not useful, and I'd rather not see it polluting a canonical thread viewed by 70k+ people. Discussions like this are exactly why many people don't comment when downvoting (in addition to revenge downvotes). – ggorlen Oct 06 '21 at 20:52
0

You could also make use of reduceRight which will iterate through each value of the array (from right-to-left)

const myArray = [1, 2, 3, 4, 5]
const reversedArray = myArray.reduceRight((acc, curr) => [...acc, curr], [])
console.log(reversedArray) // [5, 4, 3, 2, 1]
Nick D
  • 11
  • 1
0

Pasting the below into any javascript runtime console either on the browser or node.js would do a straight way benchmark test on a large array of number.

Say 40,000 in my case

var array = Array.from({ length: 40000 }, () =>
  Math.floor(Math.random() * 40000)
);
var beforeStart = Date.now();
var reversedArray = array.map((obj, index, passedArray) => {
  return passedArray[passedArray.length - index - 1];
});
console.log(reversedArray);
var afterCustom = Date.now();
console.log(array.reverse());
var afterNative = Date.now();
console.log(`custom took : ${afterCustom - beforeStart}ms`);
console.log(`native took : ${afterNative - afterCustom}ms`);

You can simply run the snippet directly to see how the custom version fare too.

0

For the sake of completeness there should be also thought about efficiency in a broader way, than only execution-time performance.

Therefore I'd like to add a swap function that is minimal slower (please test on your own using perf tools) than than the ones from the accepted answer but it has the following other beneficial properties:

  • it doesn't require a temp variable for swapping
  • it keeps the input array untouched
  • it works also with arrays of values other than numerical because it simply swaps references
  • it does not start a function call on every iteration step
  • it keeps being readable (although this could still be improved)
function fastSwapReverse (array) {
  // half length for odd arrays is added by 1
  var len = array.length;
  var half = len % 2 === 0 ? len / 2 : Math.ceil(len / 2)
  var k = 0, A = new Array(len)

  // from 0 to half swap the elements at
  // start: 0 + i
  // end: len - i -1
  for (k = 0; k < half; k++) {
    A[k] = array[len - k - 1];
    A[len - k - 1] = array[k];
  }

  return A;
}

Although the original Array.prototype.reverse method also does mutate the array

Jankapunkt
  • 8,128
  • 4
  • 30
  • 59
0

Just start copying array from the backside using a for loop and return that new array. This is efficient and has O(n) time complexity.

var array1 = ["yes", "no", "maybe", "always", "sometimes", "never", "if"];
var array2 = [5,8,2,9,5,6,3,1];

function reverseArray(arr) {
  var newArray = [];
  for (var x = arr.length - 1; 0 <= x; --x) {
    newArray.push(arr[x]);
  }
  return newArray;
}

console.log(reverseArray(array1)); // ["if", "never", "sometimes", "always", "maybe", "no", "yes"]
console.log(reverseArray(array2)) // [1, 3, 6, 5, 9, 2, 8, 5]
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Ericgit
  • 6,089
  • 2
  • 42
  • 53
0

The new Array method toReversed() returns a new array with the elements in reversed order.

const original = ["a","b", "c"];
console.log("original = ", original); 

const reversed = original.toReversed();

console.log("reversed = ", reversed); 
console.log("original = ", original);
Penny Liu
  • 15,447
  • 5
  • 79
  • 98
Dragos UniqQum
  • 388
  • 4
  • 12
-1

Here are a couple of tricks I found. Credit goes to Codemanx for the original solution of

array.sort(function() {
   return 1;
})

In Typescript, this can be simplified to just one line

array.sort(() => 1)

var numbers = [1,4,9,13,16];

console.log(numbers.sort(() => 1));

Since this will be the future of JavaScript, I thought I'd share that.

Here's another trick if your array only has 2 elements

array.push(array.shift());
Richard Hamilton
  • 25,478
  • 10
  • 60
  • 87
  • 1
    The sort approach won't reverse the array; maybe it used to do it, but it's implementation dependent. Right now I tested it in the newest stable V8 and it doesn't reverse. In fact, it the order remained the same. However, it reversed it, when I returned -1. It might be because in ES2019 the .sort method is required to perform stable sorting. But still, I wouldn't rely on that. – pepkin88 Nov 29 '19 at 10:33
  • This code is broken, as pointed out in other threads. It relies on implementation-dependent characteristics of internal sorting algorithms, so you will get different results between different environments and engine versions. Basically, if it appears to work in certain environments, that shouldn't be trusted because it breaches the sort comparator's contract. – ggorlen Oct 06 '21 at 21:03
-1
// array-reverse-polyfill.js v1
Array.prototype.reverse = Array.prototype.reverse || function() {
    return this.sort(function() {
        return -1;
    });
};

If you are in a modern browser you use the native reverse function. If it's not modern this polyfill will add this function for you. So just use it:

I haven't found any cases where Firefox does the opposite behavior to other browsers. Anyway, if you want to ensure that the function will sort alphabetically, just do a little test first.

// array-reverse-polyfill.js v1.1
;(function(){
    var order = ['a', 'b'].sort(function(a, b){
        return -1;
    });
    order = order[0] === 'b' ? -1 : 1;
    Array.prototype.reverse = Array.prototype.reverse || function() {
        return this.sort(function() {
            return order;
        });
    };
})();

Example of use:

let a = [1,2,3,4,5];
a.reverse();
console.log(a);
Luis Lobo
  • 489
  • 4
  • 7
  • This doesn't work, isn't scalable and isn't semantic. It's the same as two other posts. – ggorlen Oct 04 '21 at 17:20
  • Nothing personal, but if code is abjectly broken like this, it's a service to the community to alert other users before they use it in production and wind up messing something up badly. I did run the code in Chrome 94 and it doesn't modify the array at all (`[1,2,3,4].sort(() => 1)` is the exact code). It "worked" in FF 95, but that shouldn't be trusted. `.sort(() => 1)` abuses implementation-dependent code, so if it works, it's entirely by virtue of a specific internal sorting algorithm that could change at any moment. So, for all intents and purposes, it's completely broken. – ggorlen Oct 05 '21 at 18:29
  • You have to do `[1,2,3,4].sort(() => -1)` in Chrome 94 to reverse the array, so they're using the opposite comparator as FF, at the exact present time of writing. But seriously, don't do that either because future V8 engines may well choose a different sorting algo, and the code breaks. The fact that this _appears_ to work on certain engines in certain browsers is what makes it so dangerous and well-worth a downvote (and hopefully eventual deletion) to warn future visitors. – ggorlen Oct 05 '21 at 18:35
  • @ggorlen They can, and it's much more likely, that they never change and stay that way forever. Does not make sense. – Luis Lobo Oct 06 '21 at 11:36
  • I'm not sure how to respond -- clearly, the code is broken right now, even if the algos don't change (although they will -- V8 [switched from quicksort to timsort a few years ago](https://v8.dev/blog/array-sort) and could progress from timsort at any time). The exact opposite behavior occurs when you run the same code in FF and Chrome right now. I can't think of anything worse than that. Code is always wrong when it relies on engine implementation, even if this was correct in the current version (it's not). Beyond that, even if this worked, it's still O(n log(n)) and unsemantic. – ggorlen Oct 06 '21 at 15:30
  • Beyond that, even if this was the best answer (it's anything but), the exact same solution was already shared [two](https://stackoverflow.com/a/32280926/6243352) [times](https://stackoverflow.com/a/38319476/6243352) 6 years ago, with [comments that explain that it's broken](https://stackoverflow.com/questions/5276953/what-is-the-most-efficient-way-to-reverse-an-array-in-javascript/26088002?noredirect=1#comment104438256_32280926). – ggorlen Oct 06 '21 at 15:34
  • @ggorlen The code I made it acts on the polyfill, only when the function itself doesn't exist natively so it doesn't affect newer browsers as they already have the native reverse. Now, from everything you've said, nothing stops anyone from using this simple snippet and seeing if it works for testing. It's no use creating a solution without giving any option. – Luis Lobo Oct 07 '21 at 15:05
  • @ggorlen Sometimes people just want to copy a quick snippet and do a test, and at no point did I respond with the pretense of making something lasting perfect and all that, I just gave it an option. Even because it wasn't even in the scope of several of the things you wanted to be in my answer... – Luis Lobo Oct 07 '21 at 15:05