Source

strings/isPalindrome.js

/* eslint-disable no-plusplus */
/**
 * Checks if a given string is a palindrome (reads the same forwards and backwards).
 *
 * This implementation normalizes the input by converting to lowercase and removing
 * non-alphanumeric characters before comparison. It uses two pointers approach
 * for efficient checking.
 *
 * ## Algorithm
 * - Clean the input string by removing non-alphanumeric characters and converting to lowercase.
 * - Use two pointers (start and end) to compare characters from both ends moving inward.
 * - If any mismatch is found, return false.
 * - If all characters match, return true.
 *
 * ## Constraints
 * - Input string can contain letters, numbers, spaces, and special characters.
 * - Comparison is case-insensitive.
 * - Non-alphanumeric characters are ignored.
 * - Empty strings and single characters are considered palindromes.
 *
 * ## Edge Cases
 * - Empty string returns `true`.
 * - Single character returns `true`.
 * - Strings with only spaces/special characters return `true`.
 * - Handles Unicode characters.
 *
 * ##### Complexity:
 * - **Time**: O(n) — Where n is the length of the cleaned string.
 * - **Space**: O(n) — For storing the cleaned string.
 * @function isPalindrome
 * @memberof module:strings
 * @since 1.0.0
 * @access public
 * @author Shonjoy
 * @param {string} str - The string to check for palindrome property.
 * @returns {boolean} Returns `true` if the string is a palindrome, `false` otherwise.
 * @throws {TypeError} If input is not a string.
 * @example <caption>Basic palindrome check</caption>
 * isPalindrome("racecar");
 * // → true
 * @example <caption>Case-insensitive check</caption>
 * isPalindrome("RaceCar");
 * // → true
 * @example <caption>Ignores spaces and punctuation</caption>
 * isPalindrome("A man, a plan, a canal: Panama");
 * // → true
 * @example <caption>Non-palindrome string</caption>
 * isPalindrome("hello");
 * // → false
 * @example <caption>Empty string is palindrome</caption>
 * isPalindrome("");
 * // → true
 * @example <caption>Single character is palindrome</caption>
 * isPalindrome("a");
 * // → true
 * @example <caption>Numbers in string</caption>
 * isPalindrome("12321");
 * // → true
 * @example <caption>Real-world usage: Form validation</caption>
 * const userInput = "Was it a car or a cat I saw?";
 * if (isPalindrome(userInput)) {
 *   console.log("That's a palindrome!");
 * }
 * @see {@link isAnagram} - For checking if two strings are anagrams
 * @see {@link reverseString} - For reversing a string
 * @see {@link https://en.wikipedia.org/wiki/Palindrome|Palindrome (Wikipedia)}
 */
export const isPalindrome = (str) => {
  if (typeof str !== 'string') {
    throw new TypeError('Input must be a string');
  }

  // Clean the string: remove non-alphanumeric and convert to lowercase
  const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');

  // Empty or single character strings are palindromes
  if (cleaned.length <= 1) {
    return true;
  }

  // Two pointers approach
  let left = 0;
  let right = cleaned.length - 1;

  while (left < right) {
    if (cleaned[left] !== cleaned[right]) {
      return false;
    }
    left++;
    right--;
  }

  return true;
};