Never Use Array.from() to Convert Strings to Arrays

TL;DR: Splitting a string into an array is about 70 times faster with 'a string'.split('') than Array.from('a string').

In my interview prep I came across an article on three ways of reversing a string in JavaScript by Sonya Moisset. The problem of reversing a string may seem trivial (and it is), but I'm never "too good" to come up with an answer for a problem. The author of the article showed three perfectly acceptable ways of reversing a string, but I'll share a fourth way that I like. I've been on a .reduce binge lately, and I've been thinking of ways to shoehorn it into every iterator I write, so I figure I'll do it again here.

The normal way

If you were a sane person confronted with the problem of reversing a string, your first instinct might be to avoid reinventing the wheel and just use the built in Array.prototype.reverse method. And to do that you would have to turn your string into an array so you would probably write something like this:

String.prototype.reverseNormal = function(){  
  return this.split('').reverse().join('');
};

Easy and readable. If you wrote this on the whiteboard during an interview you'd probably get the nod. But what if you wanted a confused look instead?

The .reduce() way

String.prototype.reverseReduce = function(){  
  return Array.from(this).reduceRight( (prev, current) => { 
    return prev + current;
  });
};

This algorithm uses Array.from to convert the string into an array such that each element is a character in the string. It then calls .reduceRight which functions similarly to regular old .reduce except it iterates from right to left. Since we're not passing a second argument into .reduceRight to define a starting point, it will set prev, which is our accumulator, to the last element by default. And current, which is the current element at each call, will begin as the second to last element. At each call we build a reversed string through return prev + current by the nature of working backwards from the end of the string. Since we iterate through the array once linearly, the time complexity is O(n).

Benchmarks

I wont bury the lede: the reduce way is significantly slower. Benchmarks were run on a Cloud9 Free Workspace using Benchmark.js in Node v7.9.0. Each method was tested twice: once with a long string ('abcdefg'.repeat(99)) and once with a short string ('abcdefg'). Here are the results:

String#reverseNormal Long x 39,643 ops/sec ±4.08% (73 runs sampled)
String#reverseNormal Short x 845,394 ops/sec ±3.59% (69 runs sampled)
String#reverseReduce Long x 5,955 ops/sec ±3.70% (75 runs sampled)
String#reverseReduce Short x 175,195 ops/sec ±4.40% (68 runs sampled)
Fastest is String#reverseNormal Short

The results are fairly clear: reversing a string the normal way is faster. The bottleneck, however, seems to be in the Array.from method. I set up a test suite in which the only action was splitting a string. Two tests performed it with Array.from(someString) and two tests performed it with someString.split(''). The tested strings were the same as the ones used in the previous benchmarks.

Array#from Long x 10,322 ops/sec ±1.88% (75 runs sampled)
Array#from Short x 326,023 ops/sec ±2.11% (76 runs sampled)
String#split Long x 762,177 ops/sec ±3.33% (83 runs sampled)
String#split Short x 6,611,921 ops/sec ±2.53% (76 runs sampled)

String.prototype.split is almost 74 times faster than Array.from for converting long strings to arrays. Interestingly, if we substitute in this.split into the String.reverseReduce method instead of Array.from(this), the results get a lot closer:

String#reverseNormal Long x 27,795 ops/sec ±3.43% (69 runs sampled)
String#reverseNormal Short x 814,784 ops/sec ±5.00% (67 runs sampled)
String#reverseReduce Long x 36,910 ops/sec ±3.65% (75 runs sampled)
String#reverseReduce short x 1,061,802 ops/sec ±4.60% (68 runs sampled)

The two algorithms are almost equally performant when we do it this way. So the final .reverseReduce method looks like this:

String.prototype.reverseReduce = function(){  
  return this.split('').reduceRight( (prev, current) => { 
    return prev + current;
  });
};

Conclusion

Never use Array.from() to convert a string of any length to an array. Instead, prefer String.prototype.split. The 2016 ECMAScript spec for String.prototype.split and 2016 ECMAScript spec for Array.from are available here. Someone with a deeper understanding of JavaScript can probably do a much better job than I at explaining the performance disparity.

Never Use Array.from() to Convert Strings to Arrays
Share this