Algorithm

Valid Palindrome

Two Pointer Pattern

Valid Palindrome

A phrase reads as a palindrome if — after lowercasing every letter and throwing away every character that is not a letter or digit — the text that remains is the same read left-to-right and right-to-left. Given a string s, return true if it is a palindrome under those rules and false otherwise. Only letters (a–z, A–Z) and digits (0–9) count; spaces, punctuation, and symbols are ignored, and case is not significant. An empty string, or one that becomes empty after the non-alphanumeric characters are removed, counts as a palindrome — return true.

CONSTRAINTS
  • 1 <= s.length <= 2 × 10⁵
  • s consists of printable ASCII characters
  • Non-alphanumeric characters are ignored
  • Comparison is case-insensitive
EXAMPLE 1
Input: s = "A man, a plan, a canal: Panama"
Output: true
Keeping only letters and digits and lowercasing gives "amanaplanacanalpanama", which reads the same in both directions.
EXAMPLE 2
Input: s = "race a car"
Output: false
The kept characters are "raceacar". Reading inward from both ends, the fourth pair disagrees (e against a), so it is not a mirror.
EXAMPLE 3
Input: s = " "
Output: true
The only character is a space, which is ignored. Nothing is left to compare, so it counts as a palindrome.
EXAMPLE 4
Input: s = "0P"
Output: false
Both a digit and a letter are kept: "0p". The first and last characters differ, so it is not a palindrome.
Which characters actually get compared?
Only letters (a–z, A–Z) and digits (0–9). Every space, punctuation mark, and symbol is ignored completely.
Does capitalization matter?
No — the comparison is case-insensitive, so 'A' and 'a' count as the same character.
What should an empty string, or one with no letters or digits, return?
True. Once the ignored characters are removed there is nothing left that could mismatch, so it counts as a palindrome by convention.
Do I return the cleaned string, or just a yes/no?
Just a boolean — true if it reads the same both ways, false otherwise.

We want to know whether a phrase reads the same forwards and backwards — a palindrome. The catch is that real text is messy. "A man, a plan, a canal: Panama" is a palindrome, but only once you ignore the spaces, the comma, and the colon, and stop caring whether a letter is a capital 'A' or a small 'a'. So the rule is: look only at letters and digits, treat upper and lower case as the same, and ask whether that cleaned-up sequence is a mirror of itself.

The obvious way: clean it up, then flip it

The first idea most people have is to do the cleanup literally. Walk the string once, keep only the letters and digits, lowercase them, and build a brand-new clean string. Then make a second string that is the clean one reversed, and check whether the two are equal.

python
cleaned = [c.lower() for c in s if c.isalnum()]   # keep only letters/digits, lowercased
return cleaned == cleaned[::-1]                    # is it the same reversed?

This works, and it runs in O(N) time — we touch each character a fixed number of times. But look at the memory. We built a whole second copy of the string, and then a reversed copy on top of that. For a string of length N that is O(N) extra space. And it is a little silly: to check a mirror, we made a copy just so we could read it from both directions — when we could already read the original from both directions. A string lets you jump straight to any position by its index, so index 0 is the first character and the last index is the final one. Why copy at all?

Reading from both ends at the same time

Keep two position markers. Call one left, starting at index 0 (the first character), and one right, starting at the last index (the final character). A palindrome means the first character matches the last, the second matches the second-to-last, and so on working inward. So compare the characters under left and right. If they are equal, that pair is fine — step left one to the right and right one to the left, and check the next pair further in. If they ever disagree, the phrase is not a palindrome and we stop right there and return false. The noise is handled as we go: if left is sitting on something that is not a letter or digit, just push left inward one step without comparing; same for right. We never build a second string — we walk the original from both ends toward the middle.

python
left, right = 0, len(s) - 1
while left < right:
    if not s[left].isalnum():        # left is on noise — skip it
        left += 1
    elif not s[right].isalnum():     # right is on noise — skip it
        right -= 1
    else:                            # both on real characters — compare
        if s[left].lower() != s[right].lower():
            return False             # a mismatched pair: not a palindrome
        left += 1
        right -= 1
return True                          # the two markers met with no mismatch
Crucial Notethe loop runs only while left < right. The moment they meet or cross, every pair has already been checked, so we are done — return true. A middle character in an odd-length string never needs comparing, because it is its own mirror. And notice we compare only when both markers are on real characters: the two skip branches make sure a space on one side is never lined up against a letter on the other.
Why it is safe to keep shrinking inward

Here is the quiet reason this works. Every time a pair matches, those two characters have done their whole job — they can never be part of a future mismatch, so we throw them away and never look at them again. What is left is a smaller string in the middle, and the original is a palindrome exactly when that smaller middle is. We keep chopping one character off each end until nothing is left. The fact that stays true the entire time — call it the invariant — is this: everything outside the leftright window has already been confirmed to mirror correctly. When the window empties, the whole string is confirmed.

Worked example:"A man, a plan, a canal: Panama"
- left=0 ('A'), right=29 ('a'). Both real. 'a' == 'a' — match. Move both inward.
- left=1 (' '). A space, not a letter or digit — skip it, left=2 ('m'). right is on 'm'. Match. Move both.
- The march continues: 'a'/'a', 'n'/'n', … every letter pairs up, and every comma, space, and colon is quietly skipped along the way.
- Eventually left and right cross in the middle with no mismatch ever found. Return true.

Now a failing case, s = "race a car". Cleaned it is "raceacar": 'r'/'r' match, 'a'/'a' match, 'c'/'c' match, then left is on 'e' and right is on 'a' — a mismatch. Return false immediately, without bothering to check the rest.

This shape has a name: converging two pointers

Two markers that start at opposite ends and walk toward each other, each step letting us permanently discard at least one end — that is the converging form of the two-pointer pattern. (Its cousin, the same-direction form from Move Zeroes and Is Subsequence, had both pointers walking the same way; here they come at the problem from opposite sides.) It turns work that looks like it needs O(N²) — compare everything against everything — into a single O(N) sweep, because across the whole run each pointer only ever moves inward, so together they take about N steps in total.

Converging only works when the input has a property that lets the two ends tell you something. For a palindrome, that property is symmetry: the ends are supposed to be mirror images, so a matched pair is provably finished and a mismatched pair is a provable answer. In the sorted-array problems coming next, the property is order instead, and it will let you decide which one of the two ends to throw away. Either way, the reusable skeleton is the same:

left, right = 0, n - 1
while left < right:
    look at the pair at (left, right)
    from that pair, decide which end to discard: move left in, move right in, or both
    maybe record something

Solving a new converging problem is really just filling three blanks: (a) what you read from the two ends, (b) which pointer that tells you to move, and (c) what you record along the way. For Valid Palindrome: (a) are the two characters equal? (b) on a match move both inward, on a mismatch stop; (c) nothing to record — just answer true or false.

How to spot the next one

Reach for converging two pointers when you see: a check for symmetry or a palindrome; or a sorted array with a question about a pair (or triplet) whose values hit a target; or two boundaries — walls, the two ends of an array — where the answer lives between them. A limit like N up to 10⁵ is a hint too: it rules out an O(N²) double loop, so the interviewer is expecting a linear sweep, and coming in from both ends is one of the few ways to get there. The trap to keep in mind: the sorted-array version needs the sorting. If an array is unsorted, comparing its two ends tells you nothing about which side to drop, and the pattern falls apart — you would sort first (if you are allowed) or reach for a hash map instead. Train the reflex: whenever the useful information lives at the two ends of an ordered or symmetric thing, try walking inward from both.

Interactive Strategy Visualization

Valid Palindrome Visualization

Checking symmetry while ignoring symbols

L
M
0
a
1
d
2
a
3
m
4
,
5
6
i
7
n
8
9
E
10
d
11
e
12
n
13
,
14
15
I
16
'
17
m
18
19
A
20
d
21
a
22
R
m
23
CURRENT STEP
Start pointers at both ends.
O(N) Time · O(N) Space Clean & Reverse
O(N) Time · O(1) Space Converging Scan