Palindrome Detection Algorithms: Techniques and Trade-offs
A palindrome reads the same forward and backward — "racecar", "madam", and "A man, a plan, a canal: Panama" are all palindromes. Detecting them is a classic programming exercise, but the problem reveals interesting trade-offs in algorithm design, character handling, and real-world text processing.
The Naive Approach: Reverse and Compare
The simplest algorithm reverses the string and checks for equality:
function isPalindrome(str) {
const reversed = str.split('').reverse().join('')
return str === reversed
}
This works for clean inputs like "racecar", but fails on real-world text that includes spaces, punctuation, and mixed case. It also creates a full copy of the string, using O(n) extra memory.
Time complexity: O(n). Space complexity: O(n).
The Two-Pointer Technique
A more efficient approach uses two pointers — one starting at the beginning, one at the end — moving inward and comparing characters:
function isPalindrome(str) {
let left = 0
let right = str.length - 1
while (left < right) {
if (str[left] !== str[right]) return false
left++
right--
}
return true
}
This halves the number of comparisons and uses O(1) extra memory. No string copy is needed.
Time complexity: O(n). Space complexity: O(1).
Ignoring Non-Alphanumeric Characters
Real-world palindromes like "A man, a plan, a canal: Panama" contain spaces and punctuation that should be ignored. You can handle this by preprocessing the string or by skipping non-alphanumeric characters in the two-pointer loop:
function isPalindrome(str) {
let left = 0
let right = str.length - 1
while (left < right) {
while (left < right && !isAlphaNumeric(str[left])) left++
while (left < right && !isAlphaNumeric(str[right])) right--
if (str[left].toLowerCase() !== str[right].toLowerCase()) return false
left++
right--
}
return true
}
function isAlphaNumeric(char) {
return /[a-z0-9]/i.test(char)
}
This handles mixed case, spaces, and punctuation without creating a filtered copy of the string.
Unicode Considerations
JavaScript strings use UTF-16 encoding. Characters outside the Basic Multilingual Plane (emoji, some CJK characters) are represented as surrogate pairs. Naive reversal breaks these characters:
// Broken: splits surrogate pairs
'top🥞pot'.split('').reverse().join('') // 'top��pot' (broken pancake emoji)
// Correct: use Array.from or spread syntax
[...'top🥞pot'].reverse().join('') // 'top🥞pot' (intact)
For palindrome detection, the two-pointer approach naturally handles surrogate pairs because it compares characters from the outside in without reversing.
However, Unicode normalization adds another layer. The character é can be represented as a single code point (U+00E9) or as e + combining accent (U+0065 U+0301). These look identical but are not equal in string comparison:
const a = '\u00E9' // é (precomposed)
const b = 'e\u0301' // é (decomposed)
a === b // false!
// Normalize before comparing
a.normalize('NFC') === b.normalize('NFC') // true
For palindrome checking of user-submitted text, normalize the input first.
Recursive Approach
Palindrome detection can be expressed recursively — a string is a palindrome if its first and last characters match and the substring between them is also a palindrome:
function isPalindrome(str, left = 0, right = str.length - 1) {
if (left >= right) return true
if (str[left] !== str[right]) return false
return isPalindrome(str, left + 1, right - 1)
}
This is elegant but risks stack overflow on very long strings (JavaScript's call stack limit is typically around 10,000–25,000 frames). The iterative two-pointer approach is safer for production use.
Comparative Summary
| Algorithm | Time | Space | Notes |
|---|---|---|---|
| Reverse and compare | O(n) | O(n) | Simple, allocates copy |
| Two-pointer | O(n) | O(1) | Best for most use cases |
| Two-pointer with skip | O(n) | O(1) | Handles punctuation/case |
| Recursive | O(n) | O(n) stack | Elegant, stack overflow risk |
For most practical applications, the two-pointer approach with character skipping strikes the best balance of clarity, performance, and correctness.
Key Takeaways
- The two-pointer technique detects palindromes in O(n) time with O(1) space — no string copy needed
- Real-world palindrome checking must handle case, spaces, and punctuation by skipping non-alphanumeric characters
- UTF-16 surrogate pairs break naive string reversal — use
Array.from()or spread syntax - Unicode normalization (NFC) ensures accented characters compare correctly
- Recursive approaches are elegant but risk stack overflow on long strings
- The two-pointer method with character skipping is the best general-purpose solution
Related Guides
- Text Reversal Techniques: Methods and Applications
- Mirror Text Guide: How Reversed Text Works
- Text Manipulation Tools: A Practical Overview
Try It Yourself
Test your strings instantly with our free Text Reverser tool. Enter any text, see it reversed — and discover palindromes by comparing the original and reversed output side by side.