0
l
Ignore punctuation and case: a string is a valid palindrome if its alphanumeric characters read the same forwards and backwards. One inward pass with two pointers — no extra allocation — decides it in O(n).
Given a string s, return true if it is a palindrome after converting all uppercase letters to lowercase and removing all non-alphanumeric characters. "A man, a plan, a canal: Panama" → true (filtered: amanaplanacanalpanama). "race a car" → false (raceacar ≠ its reverse).
l = 0, r = s.length − 1. We narrow inward.s[l] is not alphanumeric, increment l. This filters spaces, punctuation, and symbols without allocating a new string.r leftward past non-alphanumeric characters.s[l].toLowerCase() !== s[r].toLowerCase(), return false immediately.l++ and r-- and repeat from step 1.l ≥ r, every alphanumeric pair matched — return true.l < r as their guard, not just l < s.length. Without this guard, both pointers can walk past each other for strings that are all non-alphanumeric (e.g. " "), and you'd compare out-of-bounds indices.The clever solution has the same asymptotic time but eliminates the hidden constant of building a cleaned string. The regex check per character is O(1) because the character set is bounded.
function isPalindrome(s: string): boolean {
let l = 0;
let r = s.length - 1;
while (l < r) {
// skip non-alphanumeric from left
while (l < r && !isAlphanumeric(s[l])) l++;
// skip non-alphanumeric from right
while (l < r && !isAlphanumeric(s[r])) r--;
if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
l++;
r--;
}
return true;
}
function isAlphanumeric(ch: string): boolean {
return /[a-zA-Z0-9]/.test(ch);
}l starts at the first character, r at the last. The outer while (l < r) loop narrows the window until the pointers meet or cross.l < r guard inside these loops is essential — it prevents overrun when the remaining window is exhausted or contains no alphanumeric characters.false — no need to continue.l < r ends the search once the window collapses.false means every alphanumeric pair matched — the string is a valid palindrome.isAlphanumeric uses a simple regex. For performance-critical code you could use char-code arithmetic instead, but the regex is clearer and interviewers prefer readable code.s[l] or s[r] and check if either remainder is a palindrome.l = 0 and r = -1 the loop condition l < r is immediately false, so it returns true — correct, since an empty string is vacuously a palindrome.(ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') on the lowercased character. Avoids regex overhead in tight loops.Array.from(s) to get code-point–safe iteration and a Unicode-aware regex like /\p{L}|\p{N}/u.| "reads the same forwards and backwards" | two pointers from ends |
| ignore spaces / punctuation / case | skip non-alphanumeric in the pointer loop |
| check symmetry without extra memory | in-place two pointers, no new string |
| "allow one deletion" | palindrome → branch on mismatch (Valid Palindrome II) |
function isPalindrome(s: string): boolean {
let l = 0, r = s.length - 1;
while (l < r) {
while (l < r && !isAlphanumeric(s[l])) l++;
while (l < r && !isAlphanumeric(s[r])) r--;
if (s[l].toLowerCase() !== s[r].toLowerCase()) return false;
l++;
r--;
}
return true;
}l >= r (pointers crossed — palindrome) or an explicit return false on mismatch."race a car" — what is the first mismatch?"A man, a plan, a canal: Panama", what is the resulting lowercase alphanumeric string?