Published on

My Experiments with Reversing a String in JavaScript

Authors

There are many ways to do a simple thing in JavaScript, like reversing a string. The question is which one is the fastest.

So, I searched Google for the most common solutions and wrote a script to compare them. This article covers that script and what its output taught me.

How did I test?

I took inspiration from one or two scripts and then built my script for testing. The core function is as follows:

function test(repeats, description, func, expected) {
  if (func() !== expected) {
    console.info('\x1b[31m%s\x1b[0m', `${description}: Result not valid`)
    return
  }

  const t1 = Date.now()
  for (let i = 0; i < repeats; ++i) {
    func()
  }
  const t2 = Date.now()
  const dt = t2 - t1
  console.log(`${description}: total time is ${dt} ms`)
  results[description] = dt
}

The test function checks whether the output is correct. If it is, it runs the function many times and measures the time.

const results = {}

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args)
    } else {
      return function (...args2) {
        return curried.apply(this, args.concat(args2))
      }
    }
  }
}

const curriedTest = curry(test)
const repeatedTest = curriedTest(1_00_000)

Further, I made some changes to suit my needs. They're irrelevant to the topic here.

Functions To Reverse A String

Method 1:  Split And Reverse

function arraySplitAndReverse(str) {
  return str.split('').reverse().join('')
}

Method 2: Spread And Reverse

function arraySpreadAndReverse(str) {
  return [...str].reverse().join('')
}

Method 3: Split And Reduce

function arraySplitAndReduce(str) {
  return str.split('').reduce((x, y) => y.concat(x))
}

Method 4: Spread and Reduce

function arraySpreadAndReduce(str) {
  return [...str].reduce((x, y) => y.concat(x))
}

Method 5: Straight 'for' Loop With String

function forLoopStraight(str) {
  let reverse = ''
  for (let i = 0; i < str.length; i++) {
    reverse += str[str.length - 1 - i]
  }
  return reverse
}

Method 6: Inverse 'for' Loop With String

function forLoopInverse(str) {
  let reverse = ''
  for (let i = str.length - 1; i >= 0; i--) {
    reverse += str[i]
  }
  return reverse
}

Method 7: Inverse 'for' Loop With Array

function forLoopInverseWithArray(str) {
  const reverse = []
  for (let i = str.length - 1; i >= 0; i--) {
    reverse.push(str[i])
  }
  return reverse.join('')
}

Method 8: Swapping Indices With 'while' Loop

function reverseBySwappingIndices(str) {
  const strArr = [...str]
  let leftIndex = 0
  let rightIndex = strArr.length - 1

  while (leftIndex < rightIndex) {
    let temp = strArr[leftIndex]
    strArr[leftIndex] = strArr[rightIndex]
    strArr[rightIndex] = temp

    leftIndex++
    rightIndex--
  }

  return strArr.join('')
}

Method 9: Swapping Indices With 'for' Loop

function divideAndConquer(str) {
  const strArr = [...str]
  let n = strArr.length - 1

  for (let i = 0; i <= n / 2; i++) {
    let temp = strArr[i]
    strArr[i] = strArr[n - i]
    strArr[n - i] = temp
  }

  return strArr.join('')
}

Method 10: Array Popping With 'while' Loop

function reverseWithWhileAndPop(str) {
  const output = []
  const strArr = [...str]
  while (strArr.length) {
    output.push(strArr.pop())
  }

  return output.join('')
}

Method 11: Array Map With Preserving Array

function mapPreservingArray(str) {
  const strArr = [...str]
  const reverse = strArr.map(strArr.pop, [...strArr])
  return reverse.join('')
}

Method 12: Array Map Without Preserving Array

function mapWithoutPreservingArray(str) {
  const strArr = [...str]
  const reverse = [...strArr].map(strArr.pop, strArr)
  return reverse.join('')
}

If you code, these snippets are self-explanatory. I tested each function 100,000 times.

// Test Functions
const testString =
  "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum"

const reverseString =
  "muspI meroL fo snoisrev gnidulcni rekaMegaP sudlA ekil erawtfos gnihsilbup potksed htiw yltnecer erom dna ,segassap muspI meroL gniniatnoc steehs tesarteL fo esaeler eht htiw s0691 eht ni desiralupop saw tI .degnahcnu yllaitnesse gniniamer ,gnittesepyt cinortcele otni pael eht osla tub ,seirutnec evif ylno ton devivrus sah tI .koob nemiceps epyt a ekam ot ti delbmarcs dna epyt fo yellag a koot retnirp nwonknu na nehw ,s0051 eht ecnis reve txet ymmud dradnats s'yrtsudni eht neeb sah muspI meroL .yrtsudni gnittesepyt dna gnitnirp eht fo txet ymmud ylpmis si muspI meroL"

repeatedTest('split() and .reverse() method', () => arraySplitAndReverse(testString), reverseString)
repeatedTest(
  '...spread and .reverse() method',
  () => arraySpreadAndReverse(testString),
  reverseString
)
repeatedTest('split() and .reduce() method', () => arraySplitAndReduce(testString), reverseString)
repeatedTest(
  '...spread and .reduce() method',
  () => arraySpreadAndReduce(testString),
  reverseString
)
repeatedTest('For loop straight', () => forLoopStraight(testString), reverseString)
repeatedTest('For loop inverse', () => forLoopInverse(testString), reverseString)
repeatedTest(
  'For loop inverse with Array',
  () => forLoopInverseWithArray(testString),
  reverseString
)
repeatedTest(
  'Swapping Indices with temp variable',
  () => reverseBySwappingIndices(testString),
  reverseString
)
repeatedTest('Divide and conquer swapping', () => divideAndConquer(testString), reverseString)
repeatedTest('while loop and .pop()', () => reverseWithWhileAndPop(testString), reverseString)
repeatedTest('.map() preserving original', () => mapPreservingArray(testString), reverseString)
repeatedTest(
  '.map() without preserving original',
  () => mapWithoutPreservingArray(testString),
  reverseString
)

The Result

Just to get comparative results, I wrote code to sort and add comparison in the form of 'slowness'.

let fastestTime = Infinity
const sortedResults = Object.entries(results)
  .map(([desc, time]) => {
    if (time < fastestTime) {
      fastestTime = time
    }

    return {
      desc,
      time,
    }
  })
  .map((result) => {
    result.slowness = Number.parseFloat(result.time / fastestTime).toFixed(2) + 'x'
    return result
  })
  .sort((a, b) => a.time - b.time)

console.table(sortedResults)
Results of Reversing a String

Conclusion

  • Methods that skip arrays are the fastest, because they avoid converting strings to arrays and back.
  • .reduce() beat the built-in .reverse() on arrays in my runs.
  • .split("") beat ...spread for converting a string into an array.
  • Working with indices cost comparatively more.