· By John Kavanagh

Valid Palindrome in JavaScript: Two Pointers and Normalisation

Abstract image used to represent Valid Palindrome in JS with Two Pointers
Image by Surendran MP.

Considered one of the 'LeetCode' type interview questions, the Valid Palindrome Problem is fairly straightforward:

Given a string, determine if it is a valid palindrome or not, considering only alphanumeric characters and ignoring case.


What is a Palindrome

Starting very simply, a palindrome is a word, phrase, number, or other sequence of characters that reads the same forwards as it does backwards. In doing this, we ignore spaces, punctuation, and capitalisation.

For example, "level," "radar," and "A man, a plan, a canal, Panama!" are all valid palindromes.


Approach

The simplest way to solve this problem is to use a twopointer approach. We start with two pointers, one at the beginning of the string and the other at the end. As we move towards the centre of the string, we compare characters at both pointers to see if they are the same.

If they match, we continue; if they don't match then we know that the string is not a palindrome. As I mentioned: we ignore nonalphanumeric characters and punctuation whilst comparing the characters.


Implementation

The TypeScript function below normalises ASCII letters and digits before comparing them from both ends. The cleaned string takes O(n) extra space for an input of length n; the twopointer comparison then uses constant working space. This is the exercise's ASCII rule, not general Unicode normalisation.

export function isValidPalindrome(s: string): boolean {
  // Convert the string to lowercase and remove non-alphanumeric characters
  const cleanString = s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();

  // Initialise two pointers
  let left = 0;
  let right = cleanString.length - 1;

  // Check if the characters at both pointers match
  while (left < right) {
    if (cleanString[left] !== cleanString[right]) {
      return false;
    }
    left++;
    right--;
  }

  return true;
}

Testing the Function

If you want to really impress your interviewers, then adding some test coverage to your function using Jest will be sure to do so, whilst also making sure that the function works correctly. This can be done very easily using examples that we already know are valid palindromes or not:

import { isValidPalindrome } from './palindrome';

describe('Valid Palindrome', () => {
  it('should return true for valid palindromes', () => {
    expect(isValidPalindrome('level')).toBe(true);
    expect(isValidPalindrome('A man, a plan, a canal, Panama!')).toBe(true);
    expect(isValidPalindrome('Able was I, ere I saw Elba!')).toBe(true);
  });

  it('should return false for non-palindromes', () => {
    expect(isValidPalindrome('hello')).toBe(false);
    expect(isValidPalindrome('algorithm')).toBe(false);
    expect(isValidPalindrome('A palindrome is not always a palindrome!')).toBe(
      false
    );
  });
});

An Alternative Solution: Number‑Based Palindromes

For a numeric variant, assume a nonnegative safe integer written in ordinary decimal form. For example, 1001 is a palindrome and 1002 is not. A numeric value cannot preserve leading zeros, so use a string if those digits matter.

We can check that decimal representation by converting it to a string, reversing the characters and comparing the two strings:

const isNumberPalindrome = (num: number): boolean => {
  const numStr = num.toString();
  const reversedNumStr = numStr.split('').reverse().join('');
  return numStr === reversedNumStr;
};

Here, we:

  1. Use num.toString() to convert the input number num to a string. This allows us to manipulate individual digits more easily.
  2. We split the string (numStr) into an array of characters, reverse the array, and then use join to turn it back into a string. This gives us the reversed version of the original number, as a string.
  3. Finally, we do a simple comparison between numStr and reversedNumStr, and return the result. If they are equal, then the input was a valid palindrome and the function returns true.

This is concise, but it still takes linear time in the number of digits and allocates a character array and strings. It is not automatically faster than the twopointer check. Choose it for the simpler input contract and readability, rather than an unmeasured speed advantage.


The Wrap‑up

The twopointer approach is a very common and incredibly useful coding pattern, and the Valid Palindrome Problem is (from my experience) an increasingly common interview question. Suffice it to say: I was offered the job!


Untangling a delivery problem?

Send the symptoms, constraints, and affected routes. I'll help identify whether the issue sits in the application, platform, content model, deployment path, or search surface.