DEV Community

Nicholas G
Nicholas G

Posted on • Edited on

JavaScript Palindrome Checker

Hi all! 😁

Together, let's walk through one possible solution for a common algorithm: the Palindrome Checker.


The Objective

Using Javascript, create a function that receives one argument, a string. The function should return true if the string is a palindrome and return false if the string is not a palindrome.


What is a Palindrome?

According to Merriam-Webster, a palindrome is:

any word, number, or phrase that reads the same backward or forward.

Some basic examples are: mom, civic, kayak, Hannah, and 1881. More complicated examples are phrases with spaces such as: "race car" and "taco cat". Even more complicated (and often silly) examples are phrases that ignore spacing and punctuation such as: "Yo, Banana Boy!", "A man, a plan, a canal, Panama!", and "Madam, in Eden, I’m Adam."

So, how can we create a function that returns true for each of these cases?

Let's get started.


Writing our Test Cases

Before we begin, we'll write some test cases in order to check our code as we go using console.log().

In order to view the console output, install and run Node JS or use a sandbox like codesandbox.io.

Our tests:

  console.log("Expecting: false");
  console.log("Test 1:", isPalindrome('testing'))

  console.log('Expecting: true');
  console.log('Test 2:', isPalindrome('mom'))

  console.log("Expecting: true");
  console.log("Test 3:", isPalindrome("kayak"));

  console.log("Expecting: true");
  console.log("Test 4:", isPalindrome("1881"));

  console.log("Expecting: true");
  console.log("Test 5:", isPalindrome("Hannah"));

  console.log("Expecting: true");
  console.log("Test 6:", isPalindrome("race car"));

  console.log("Expecting: true");
  console.log("Test 7:", isPalindrome("A man, a plan, a canal, Panama!"));

  console.log("Expecting: true");
  console.log("Test 8:", isPalindrome("  Madam, in Eden, I’m Adam."));
Enter fullscreen mode Exit fullscreen mode

Writing our Function

We'll start with the basic function syntax, taking our string as an argument.

function isPalindrome(string) {
}
Enter fullscreen mode Exit fullscreen mode

Next, using the spread operator, we'll spread our string into a new array and save this to a variable, stringArray.

function isPalindrome(string) {
  const stringArray = [...string]; //NEW!
}
Enter fullscreen mode Exit fullscreen mode

We'll create a new empty array to hold our reversed string.

function isPalindrome(string) {
  const stringArray = [...string];
  const newArray = []; //NEW!
}
Enter fullscreen mode Exit fullscreen mode

Then, using a for-each loop, we'll iterate over each index in our stringArray and, using .unshift(), add that element to the beginning of newArray, effectively reversing the string.

function isPalindrome(string) {
  const stringArray = [...string];
  const newArray = [];
  stringArray.forEach(element => { //NEW!
    newArray.unshift(element);     //NEW!
  });                              //NEW!
}
Enter fullscreen mode Exit fullscreen mode

We can now call the .join() method on newArray, with an empty string as a parameter, in order to concatenate all the elements to form a new string. We'll save this to a variable reversedString.

function isPalindrome(string) {
  const wordArray = [...string];
  const newArray = [];
  wordArray.forEach(index => {
    newArray.unshift(index);
  });
  const reversedString = newArray.join(''); //NEW!
}
Enter fullscreen mode Exit fullscreen mode

To close out our function, we'll compare our original string with the new reversedString using the strict equality operator ===. This will return a boolean, true or false. Include the return statement in order to return the boolean, thus indicating whether the string is a palindrome or not.

function isPalindrome(string) {
  const wordArray = [...string];
  const newArray = [];
  wordArray.forEach(index => {
    newArray.unshift(index);
  });
  const reversedString = newArray.join('');  //NEW!
  return string === reversedString;          //NEW!
}
Enter fullscreen mode Exit fullscreen mode

⛔️ But wait! ⛔️
When we check this against our tests, only tests 1 through 4 pass!

So far, our code does not account for capital letters, spacing, or punctuation.

In order for a string like Hannah to pass the test, we need to convert all of our letters to lowercase by calling the .toLowerCase() method on our string. We then save it to the string variable to update this value.

function isPalindrome(string) {
  string = string.toLowerCase();  //NEW!
  const wordArray = [...string];
  const newArray = [];
  wordArray.forEach(index => {
    newArray.unshift(index);
  });
  const reversedString = newArray.join('');  
  return string === reversedString;         
}
Enter fullscreen mode Exit fullscreen mode

We now see that test 5 has passed!

To exclude punctuation and white-spaces, we can call the .replace() method and use a regular expression pattern as the first parameter to identify these characters.

In our code, this method will look like: .replace(/\W/g, '')

Let's break this down:

  • RegEx patterns are enclosed in two slashes //.

  • \W is a RegEx pattern for "non-alphanumeric" characters.

  • The g flag follows the RegEx expression and performs a global search, looking in the whole string and returning all matches.

  • We set the second parameter of our .replace() method to an empty string in order to "erase" these characters from our updated string.

When we put it all together, we get:

function isPalindrome(string) {
  string = string.toLowerCase().replace(/\W/g, '');  //NEW!
  const stringArray = [...string];
  const newArray = [];
  stringArray.forEach(index => {
    newArray.unshift(index);
  });
  const reversedString = newArray.join('');
  console.log(string);
  return string === reversedString;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we run our tests againβ€” and voila! πŸŽ‰
All 8 of our tests pass our Palindrome Checker 😁

Top comments (6)

Collapse
 
lexlohr profile image
Alex Lohr

Nice explanation. One thing that could be improved is your "tests": did you know about console.assert(shouldBeTrue, "Error message if falsy")? This works both in node and in the browser. Array.prototype.reverse() was already mentioned.

Another classical approach would be a letter-wise comparison until the middle of the string with an early return:

for (let i = 0, l = string.length >> 1; i < l; I++) {
  if (string.charAt(i) !== string.charAt(string.length - 1 - i)) {
    return false;
  }
}
return true;
Enter fullscreen mode Exit fullscreen mode

>> is the bitwise shift right operator, shifting all bits right once is the same as a floored division by two.

Collapse
 
nicholasg profile image
Nicholas G

Cool! Didn't know about console.assert() before. Thanks!

Collapse
 
fjones profile image
FJones

Since the underscore needs a special case, why is there no test for it?

Also: \W equates to [^A-Za-z0-9_] - which also strips a lot of non-latin alphabet characters. If we consider that an acceptable restriction, using the more explicit /[^a-z0-9]/ig (or /[^A-Za-z0-9]/g) would seem to be a better option, rather than the special case for the underscore. (Besides, the final code snippet is, in fact, missing the underscore in the pattern ;) )

For performance reasons we might also want to cache the compiled regex.

Collapse
 
nicholasg profile image
Nicholas G

You're right!! To simplify and keep consistent, I'll just remove the check for underscore entirely, but thanks for the good tips!

Collapse
 
gilfewster profile image
Gil Fewster

Really well explained example. A great introduction to string and array manipulation for beginners, and some regexp for good measure!

For the sake of completeness, and also to offer a more concise option for the solution, you may want to also show an alternate version which uses string.split() and array.reverse() β€” which would also be nice way to demonstrate chaining functions

const reversed = originalString.replace(regexp).split(β€œβ€).reverse().join(β€œβ€)
Enter fullscreen mode Exit fullscreen mode
Collapse
 
nicholasg profile image
Nicholas G

Thanks for the feedback! You're right, maybe I'll make another post using an alternative solution.