You've solved Valid Palindrome. Your solution works on "racecar" and "A man, a plan, a canal: Panama." You submit.
Wrong Answer.
Test case: "" (empty string)
Expected: true
Your output: Crash or false
Or test case: " " (single space)
Expected: true
Your output: false
Or test case: ".," (only punctuation)
Expected: true
Your output: false
Edge cases are the silent killers of palindrome solutions. The core logic is simple, but handling empty strings, single characters, spaces, and non-alphanumeric characters correctly is where most solutions fail.
This guide will teach you comprehensive edge case handling for Valid Palindrome, the exact checks you need, and how to write a bulletproof solution.
TL;DR
Edge cases to handle:
- Empty string →
true - Single character →
true - Only spaces/punctuation →
true(after cleaning) - Mixed case → Convert to lowercase
- Non-alphanumeric → Filter out
Key insight: Clean the string first (remove non-alphanumeric, convert to lowercase), then check if it's a palindrome.
The Problem: What Makes a Valid Palindrome?
Definition
A string is a palindrome if it reads the same forward and backward, ignoring:
- Case (uppercase vs lowercase)
- Non-alphanumeric characters (spaces, punctuation, etc.)
Examples
Valid palindromes:
"A man, a plan, a canal: Panama"→"amanaplanacanalpanama"(palindrome ✓)"race a car"→"raceacar"(not palindrome ✗)""→""(empty, palindrome ✓)" "→""(only space, palindrome ✓)"a"→"a"(single char, palindrome ✓)
Edge Case 1: Empty String
The Test Case
s = ""Expected: true
Why? An empty string reads the same forward and backward (vacuously true).
Common Mistake
def isPalindrome(s):
# Doesn't handle empty string
left, right = 0, len(s) - 1 # right = -1 for empty string!
while left < right:
# ...Problem: When s = "", right = -1, which is a valid index in Python (wraps around), causing unexpected behavior.
Correct Handling
def isPalindrome(s):
# Clean the string first
cleaned = ''.join(c.lower() for c in s if c.isalnum())
# Empty string is a palindrome
if len(cleaned) == 0:
return True
# Or just proceed with two pointers (handles empty automatically)
left, right = 0, len(cleaned) - 1
while left < right:
if cleaned[left] != cleaned[right]:
return False
left += 1
right -= 1
return TrueBetter: The loop condition left < right handles empty strings automatically (loop doesn't run).
Edge Case 2: Single Character
The Test Case
s = "a"
s = "A"
s = "1"Expected: true
Why? A single character is always a palindrome.
Common Mistake
# Doesn't explicitly handle single character
# But usually works if loop condition is correctCorrect Handling
# After cleaning
if len(cleaned) <= 1:
return True
# Or let the loop handle it (left >= right immediately)Best practice: Explicit check for len <= 1 makes intent clear.
Edge Case 3: Only Spaces and Punctuation
The Test Cases
s = " "
s = ".,!?"
s = " "
s = "!!!"Expected: true (all become empty string after cleaning)
Why? After removing non-alphanumeric characters, the string is empty, which is a palindrome.
Common Mistake
def isPalindrome(s):
# Doesn't clean the string
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]: # Compares spaces/punctuation!
return False
left += 1
right -= 1
return TrueProblem: Compares non-alphanumeric characters, which shouldn't be considered.
Correct Handling
# Clean the string first
cleaned = ''.join(c.lower() for c in s if c.isalnum())
# Now check if cleaned string is a palindromeEdge Case 4: Mixed Case
The Test Cases
s = "A"
s = "Aa"
s = "RaceCar"Expected: true (case-insensitive)
Why? The problem ignores case differences.
Common Mistake
# Doesn't convert to lowercase
if s[left] != s[right]: # 'A' != 'a' → False!
return FalseCorrect Handling
# Convert to lowercase during cleaning
cleaned = ''.join(c.lower() for c in s if c.isalnum())Edge Case 5: Non-Alphanumeric Characters
The Test Cases
s = "A man, a plan, a canal: Panama"
s = "race a car"
s = "0P"Expected:
"A man, a plan, a canal: Panama"→true"race a car"→false"0P"→false
Common Mistake
# Doesn't filter non-alphanumeric
# Compares spaces, commas, colons, etc.Correct Handling
# Filter out non-alphanumeric during cleaning
cleaned = ''.join(c.lower() for c in s if c.isalnum())The Complete, Bulletproof Solution
Approach 1: Clean First, Then Check
def isPalindrome(s: str) -> bool:
# Step 1: Clean the string
# - Convert to lowercase
# - Keep only alphanumeric characters
cleaned = ''.join(c.lower() for c in s if c.isalnum())
# Step 2: Check if cleaned string is a palindrome
left, right = 0, len(cleaned) - 1
while left < right:
if cleaned[left] != cleaned[right]:
return False
left += 1
right -= 1
return TrueWhy this works:
- ✅ Handles empty string (loop doesn't run)
- ✅ Handles single character (loop doesn't run)
- ✅ Handles only spaces/punctuation (becomes empty after cleaning)
- ✅ Handles mixed case (converted to lowercase)
- ✅ Handles non-alphanumeric (filtered out)
Approach 2: Skip Non-Alphanumeric On-the-Fly
def isPalindrome(s: str) -> bool:
left, right = 0, len(s) - 1
while left < right:
# Skip non-alphanumeric from left
while left < right and not s[left].isalnum():
left += 1
# Skip non-alphanumeric from right
while left < right and not s[right].isalnum():
right -= 1
# Compare (case-insensitive)
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return TrueWhy this works:
- ✅ Doesn't create a new string (O(1) space)
- ✅ Skips non-alphanumeric characters
- ✅ Handles all edge cases
Trade-off: More complex logic, but O(1) space instead of O(n).
JavaScript Implementation
function isPalindrome(s) {
// Clean the string
const cleaned = s
.toLowerCase()
.split('')
.filter(c => /[a-z0-9]/.test(c))
.join('');
// Check palindrome
let left = 0, right = cleaned.length - 1;
while (left < right) {
if (cleaned[left] !== cleaned[right]) {
return false;
}
left++;
right--;
}
return true;
}Java Implementation
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
// Skip non-alphanumeric from left
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
left++;
}
// Skip non-alphanumeric from right
while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
right--;
}
// Compare (case-insensitive)
if (Character.toLowerCase(s.charAt(left)) !=
Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}Edge Case Test Suite
Test your solution with these cases:
test_cases = [
# Edge case: empty string
("", True),
# Edge case: single character
("a", True),
("A", True),
("1", True),
# Edge case: only spaces
(" ", True),
(" ", True),
# Edge case: only punctuation
(".,!?", True),
("!!!", True),
# Edge case: mixed case
("Aa", True),
("RaceCar", True),
# Normal cases
("A man, a plan, a canal: Panama", True),
("race a car", False),
("racecar", True),
("hello", False),
# Edge case: numbers
("0P", False),
("121", True),
# Edge case: two characters
("ab", False),
("aa", True),
]
for s, expected in test_cases:
result = isPalindrome(s)
status = "✓" if result == expected else "✗"
print(f"{status} isPalindrome('{s}') = {result} (expected {expected})")Common Mistakes Summary
| Mistake | Problem | Fix |
|---|---|---|
| Not cleaning string | Compares non-alphanumeric | Filter with isalnum() |
| Not converting case | 'A' != 'a' | Use .lower() |
| Not handling empty | Index -1 or crash | Check len <= 1 or rely on loop |
| Not handling spaces | Compares spaces | Filter during cleaning |
| Wrong loop condition | Processes middle twice | Use left < right, not <= |
Time and Space Complexity
Approach 1 (Clean first):
- Time: O(n) - one pass to clean, one pass to check
- Space: O(n) - new cleaned string
Approach 2 (Skip on-the-fly):
- Time: O(n) - one pass with skipping
- Space: O(1) - no new string
Practice Strategy
To master palindrome edge cases:
- Solve Valid Palindrome (#125) - basic version
- Solve Valid Palindrome II (#680) - allow one deletion
- Test with all edge cases from this guide
- Write your own test suite before submitting
- Compare both approaches (clean first vs skip on-the-fly)
FAQ
Q: Is an empty string a palindrome?
A: Yes. It reads the same forward and backward (vacuously true).
Q: What about a string with only spaces?
A: After removing non-alphanumeric characters, it becomes empty, which is a palindrome.
Q: Should I use the O(1) space approach?
A: If the problem requires O(1) space, yes. Otherwise, cleaning first is simpler and more readable.
Q: How do I handle Unicode characters?
A: Use isalnum() which handles Unicode. For ASCII-only, you can use regex [a-zA-Z0-9].
Q: What if the problem allows one character deletion?
A: That's Valid Palindrome II (#680). You need a helper function to check if a substring is a palindrome after skipping one character.
Conclusion
Valid Palindrome is simple in concept but tricky in edge cases. The key is comprehensive cleaning and correct loop conditions.
Edge cases to handle:
- ✅ Empty string
- ✅ Single character
- ✅ Only spaces/punctuation
- ✅ Mixed case
- ✅ Non-alphanumeric characters
The bulletproof pattern:
# Clean the string
cleaned = ''.join(c.lower() for c in s if c.isalnum())
# Check palindrome
left, right = 0, len(cleaned) - 1
while left < right:
if cleaned[left] != cleaned[right]:
return False
left += 1
right -= 1
return TrueWhy it works:
- Handles all edge cases automatically
- Clean, readable code
- Easy to test and debug
Master these edge cases, and you'll never fail a palindrome problem again. For more on two pointers, see the complete guide and opposite direction template.
Next time you see a palindrome problem, remember: clean first, then check.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
