How to reverse a string in JavaScript

Sai gowtham on November 28, 2018

In this tutorial, we are going to learn three different ways to reverse a string in JavaScript by using the reverse method, reduce method. ... [Read Full]
markdown guide
 

Have you looked at the performance impact of each method?

 

I think something like this would actually be the most performant:

function reverseStr(str){
  let newStr = ""
  for (let i = str.length-1; i >= 0; i--){
    newStr += str[i]
   }
   return newStr
}
 

My attempt:

function reverseStr(str) {
    let newStr = str.split('');

    for (let j = str.length - 1, i = 0; i < j; i++, j--) {
        let temp = newStr[i];
        newStr[i] = newStr[j];
        newStr[j] = temp;
    }

    return newStr.join('');
}

That basically reimplements Array#reverse() from JS land at the second step.

Array#reverse() operates in-place, so there is no benefit here — you are doing exactly what .reverse() already does.

This approach is unlikely to be more performant than the built-in method, and just takes more code to write and maintain.

Yep. Also remember your usage scenario.
Are you in a browser ?
How large is the string ?
How many strings ?

Because if you're only reversing a 100 chars string, even 1000 chars, the implementation (amongst thoses proposed in this thread) doesn't matter much performance wise (memory, duration, cpu cycles)...
For clarity and code comprehension, it's something else sure ^

This is a showcase, not a contest. Also, there is no point using reverse() to do a reverse operation. It's the same as trying to explain some term using the same term in a sentence.

This would be best solution. Yes it’s a re implementation of array reverse but it skips the split and join and only uses 2 bytes of memory and n/2 time

 
 

the first is the most straightforward, while the second is going to be the fastest by far compared to #1 and #3

 

the second is going to be the fastest by far compared to #1 and #3

No, it isn't. Symbol-by-symbol string concatenation comes with significant penalties outside of microbenchmarks that immediately throw the result out.

Perhaps, but reduce is more than a power of magnitude faster than split reverse join. It’s on jsperf

That microbench is taken out of context, «faster» there does not actually mean anything.

In an actual application where you would actually use the result instead of throwing it out immediately, char-by-char concatenation would consume up to an order of magnitude more memory and slow everything down.

My code to repoduce that is in a comment below, in another thread here.

 

regular for loop will beat all these methods by huge margin
function reverse(s) {
var o = '';
for (var i = s.length - 1; i >= 0; i--)
o += s[i];
return o;
}

 
 

Anything that is using symbol-by-symbol string concatenation would build a large number of strings instead of a single one that 'join' makes and would chew memory like crazy on large strings.

You might save a tiny portion of time on a single operation in a microbench, but gc kicking in sooner would destroy that benefit and will make matters worse.

The first variant here (split/reverse/join) is both the shortest, the most readable, and the less memory consuming one.

 

Here is an test that reverses 100 different strings with length 200000 (±6):

const base = require('crypto').randomBytes(2e5).toString();
const m0 = process.memoryUsage().rss;
const t0 = process.hrtime();
const arr = [];
for (let i = 0; i < 100; i++) {
  const str = `${i}${base}${i}`;
  const reversed = str.split('').reverse().join(''); // +103 MiB, 3.7 seconds
  //const reversed = [...str].reverse().join(''); // +118 MiB, 3.9 seconds
  //const reversed = [...str].reduce((prev,next)=>next+prev); // +1080 MiB, 6.0 seconds
  //const reversed = reverseString(str); // +1105 MiB, 6.5 seconds
  arr.push(reversed);
}
const t1 = process.hrtime(t0);
const m1 = process.memoryUsage().rss;
console.log((m1 - m0) / 2**20, t1[0] + t1[1] * 1e-9);

Note that unlike a very simple microbench, it actually retains those strings (i.e. puts the results into an array), which is a bit closer to what you might see in actual application.

The first method consumes 103 MiB.

The char-by-char concat methods consume over 1 GiB, and are slower.
Also, longer and less readable.


Bonus: try replacing 100 with 200 (the number of strigs in the for loop).

split/reverse/join consumes 170 MiB and 7 seconds.

The last two (reduce and «reverseString») die with OOM after 19 seconds.


That is also valid for shorter strings, e.g. try with 10000 (and 50000) of strings with length = 1000.

10000 iterations, 1000 length = 50 MiB / 1 second vs 520 MiB / 2 seconds.
50000 iterations, 1000 length = 129 MiB / 5 seconds vs OOM / 14 seconds.

 

The first one is the most readable (hence the best).

The second and the third methods can be combined into a more readable one liner:

[...str].reverse().join('')
 
 

I like the first way best, because it's explicit and a newcomer could read it without having to learn much.

 

The fun way:


function reverseFun(str) {
    const rest = str.length > 1 ? reverseFun(str.substring(1)) : ''
    return rest + str[0] || ''
}
 

I prefer writing the base case in a guard clause. I think it is more elegant.

const reverseString = cs => {
  if (cs.length <= 0) return '';
  return reverseString(cs.substring(1)) + cs[0];
};
 

const reverseString = cs => cs.length <=0
? ''
: reverseString(cs.substring(1)) + cs[0];

 
 

Recursion:

const r = ([h,...t],o='')=>h?r(t,h+o):o; r('hello')

Recursion if we don't care about TCO:

const r = ([h,...t])=>h?r(t)+h:''; r('hello')
 
 

Another hilarious one:

Array.prototype.map.call('hello',(_,i,all)=>all[all.length-1-i]).join('')
 

If we avoid Unicode, this should probably be fast:

Buffer.from('hello','latin1').reverse().toString('latin1')
 

What about space and time complexities?
I don't know a lot of JS internals, but judging by the fact that JS strings are immutable, 2nd and 3rd are gonna take O(n2).

 
 

String.prototype.reverse = function() {
var s = "";
var i = this.length;
while (i>0) {
s += this.substring(i-1,i);
i--;
}
return s;
}

we can use this also.

 
 

Regardless of the method used, it's best to also test it with unicode characters, unless you can foresee that you'll never need unicode.

code of conduct - report abuse