Two Pointers on strings moves inward from both ends to compare or swap characters.
Focus on recognizing:
“Palindrome” / “reverse” / “compare ends” → left at 0, right at n−1
Pattern 1: Valid Palindrome
Watch both pointers converge on "level" — every pair matches, so it’s a palindrome. Press ▶ to animate.
⚠️ Animation & Content Notice
The animation work is not fully finished — some animations may have slight errors.
If there is a major error in the content or if the animation or content is difficult to understand, please contact us at rayyancodingschool@gmail.com.
Valid Palindrome
Check if a string reads the same forwards and backwards. Two pointers start at the ends and walk inward, comparing mirrored characters; a single mismatch means it is not a palindrome.
Input: l e v e l. Left starts at 0, Right at 4. Compare the mirrored pair, and if they match move both inward. When the pointers cross the middle, every pair matched. The highlighted cells are the current L/R pair; L/R labels mark the pointers.
1
left = 0, right = n - 1
2
while left < right:
3
if s[left] != s[right]: return false
4
left++, right--
5
return true
public boolean isPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
while (left < right
&& !Character.isLetterOrDigit(s.charAt(left)))
left++; // skip non-alphanumeric
while (left < right
&& !Character.isLetterOrDigit(s.charAt(right)))
right--;
if (Character.toLowerCase(s.charAt(left))
!= Character.toLowerCase(s.charAt(right))) {
return false;
}
left++;
right--;
}
return true;
}def is_palindrome(s):
left, right = 0, len(s) - 1
while left < right:
while left < right and not s[left].isalnum():
left += 1 # skip non-alphanumeric
while left < right and not s[right].isalnum():
right -= 1
if s[left].lower() != s[right].lower():
return False
left += 1
right -= 1
return Truebool isPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
while (left < right && !isalnum(s[left]))
left++; // skip non-alphanumeric
while (left < right && !isalnum(s[right]))
right--;
if (tolower(s[left]) != tolower(s[right]))
return false;
left++;
right--;
}
return true;
}function isPalindrome(s) {
const isAlnum = (c) => /[a-z0-9]/i.test(c);
let left = 0,
right = s.length - 1;
while (left < right) {
while (left < right && !isAlnum(s[left])) left++;
while (left < right && !isAlnum(s[right])) right--;
if (s[left].toLowerCase() !== s[right].toLowerCase())
return false;
left++;
right--;
}
return true;
}Skip invalid characters inside the loop — bounds check (
left < right) on every innerwhile.
Compare ends → move both inward → stop when they meet. That’s the whole pattern.
Pattern 2: Reverse String (In-Place)
public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
}def reverse_string(s):
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1void reverseString(vector<char>& s) {
int left = 0, right = s.size() - 1;
while (left < right) {
swap(s[left], s[right]);
left++;
right--;
}
}function reverseString(s) {
let left = 0,
right = s.length - 1;
while (left < right) {
[s[left], s[right]] = [s[right], s[left]];
left++;
right--;
}
}Pattern 3: Valid Palindrome II (Delete At Most One)
On the first mismatch, try skipping EITHER side — one of the two must be a palindrome:
public boolean validPalindrome(String s) {
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return isRange(s, left + 1, right)
|| isRange(s, left, right - 1);
}
left++;
right--;
}
return true;
}
private boolean isRange(String s, int i, int j) {
while (i < j) {
if (s.charAt(i++) != s.charAt(j--)) return false;
}
return true;
}def valid_palindrome(s):
def is_range(i, j):
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return is_range(left + 1, right) \
or is_range(left, right - 1)
left += 1
right -= 1
return Truebool isRange(string& s, int i, int j) {
while (i < j) {
if (s[i++] != s[j--]) return false;
}
return true;
}
bool validPalindrome(string s) {
int left = 0, right = s.size() - 1;
while (left < right) {
if (s[left] != s[right]) {
return isRange(s, left + 1, right)
|| isRange(s, left, right - 1);
}
left++;
right--;
}
return true;
}function validPalindrome(s) {
const isRange = (i, j) => {
while (i < j) {
if (s[i++] !== s[j--]) return false;
}
return true;
};
let left = 0,
right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) {
return isRange(left + 1, right) || isRange(left, right - 1);
}
left++;
right--;
}
return true;
}Both skip options are needed — you can’t know which side holds the “bad” character without checking.
Common Mistakes
Unbounded inner skips.
Every inner while needs its own left < right guard or you walk off the string.
Trying only one deletion side in Palindrome II.
"cbbcc" fails if you only skip the left — you must try both branches.
Reversing with an extra array.
The whole point is O(1) space — swap in place.
Complexity
| Pattern | Time | Space |
|---|---|---|
| Valid palindrome | O(n) | O(1) |
| Reverse | O(n) | O(1) |
| Palindrome II | O(n) | O(1) |
Premium Content
Unlock Two Pointers and all premium lessons with a subscription.
From ₹199.99/year — See plans