Check If a Linked List Is a Palindrome: Stack, Reverse-Half, and Recursive Approaches
Palindrome linked list (LeetCode #234) is a classic interview question that tests whether you can adapt array-style palindrome thinking to a structure where you can’t index backwards. The naive solution (copy to array) gives O(n) time and O(n) space; the elegant solution (reverse the second half in place) gives O(n) time and O(1) space, which is what most interviewers want. This guide covers both, with working code, the reversal trick, and the recursive variant.
Problem Statement
Given the head of a singly linked list, return True if the list reads the same forwards and backwards, False otherwise.
Examples:
1 → 2 → 2 → 1→ True1 → 2→ False1 → 2 → 1→ True (odd-length palindrome)1→ True
Approach 1: Copy to Array (O(n) Space)
Convert to a list, then check using two pointers. Easiest to write; O(n) extra space.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def is_palindrome_array(head: ListNode) -> bool:
"""O(n) time, O(n) space."""
values = []
while head:
values.append(head.val)
head = head.next
return values == values[::-1]
Acceptable as a warm-up; interviewers will push for O(1) space.
Approach 2: Reverse Second Half (Standard Answer, O(1) Space)
Find the middle, reverse the second half, then compare halves node-by-node. This achieves O(n) time and O(1) space without modifying the input destructively (you can restore the second half before returning if needed).
def is_palindrome(head: ListNode) -> bool:
"""O(n) time, O(1) space."""
if not head or not head.next:
return True
# Phase 1: find middle (slow ends at middle for odd, end-of-first-half for even)
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# Phase 2: reverse the second half
second_half = reverse(slow.next)
# Phase 3: compare halves
p1, p2 = head, second_half
while p2:
if p1.val != p2.val:
return False
p1 = p1.next
p2 = p2.next
return True
def reverse(head: ListNode) -> ListNode:
"""Standard linked-list reversal."""
prev = None
current = head
while current:
next_node = current.next
current.next = prev
prev = current
current = next_node
return prev
Complexity: O(n) time (three linear passes), O(1) space (just pointers).
Why this works
After phase 1, slow points to the middle of the list (the last node of the first half for odd length; the last node of the first half for even length, which is the same as “node n/2 from the start”). Reversing the second half makes it walkable from the end backwards. Comparing forward through both halves checks palindrome property.
The two halves don’t have to be exactly equal in length:
- Even length n: first half has n/2 nodes, second half has n/2 nodes.
- Odd length n: first half has (n+1)/2 nodes (including middle), second half has (n-1)/2 nodes. The middle is in the first half and naturally gets skipped in the comparison loop because we exit when
p2is None.
Approach 3: Recursive (O(n) Space, but Conceptually Different)
Use recursion to walk to the end. As the call stack unwinds, compare the unwinding pointer to a forward pointer. O(n) space due to call stack; useful when the interviewer asks for a recursive solution.
def is_palindrome_recursive(head: ListNode) -> bool:
"""O(n) time, O(n) space (recursion)."""
front = [head]
def check(node: ListNode) -> bool:
if not node:
return True
if not check(node.next):
return False
if front[0].val != node.val:
return False
front[0] = front[0].next
return True
return check(head)
The trick: the recursion lets you walk backwards through the list “for free” by leveraging the call stack. The mutable container front tracks the forward pointer across recursive calls. Elegant but uses O(n) stack space.
Restoring the List After Reversal
The reverse-second-half approach mutates the input. If the caller expects the list unchanged, restore it before returning:
def is_palindrome_restoring(head: ListNode) -> bool:
if not head or not head.next:
return True
slow = fast = head
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
second_half = reverse(slow.next)
second_head = second_half # save for restoration
p1, p2 = head, second_half
result = True
while p2:
if p1.val != p2.val:
result = False
break
p1 = p1.next
p2 = p2.next
# Restore: reverse second half back
slow.next = reverse(second_head)
return result
Strong candidates mention this when discussing the trade-off; some interviewers explicitly require non-destructive solutions.
Common Variations
Doubly linked list palindrome check
Trivial: walk forward and backward simultaneously with two pointers. O(n) time, O(1) space, no reversal needed.
Palindrome with comparison key
Sometimes you compare a derived value (e.g., parity, hash) rather than the raw value. Same algorithm; replace p1.val != p2.val with the appropriate comparison.
Find the middle of a linked list
(LeetCode #876) Standalone problem. The fast/slow pointer trick used here is the canonical answer.
Reverse a linked list
(LeetCode #206) The reverse helper used here is itself a classic interview question. Often asked together as a sequence: “reverse a linked list” → “find the middle” → “check if palindrome.”
Common Mistakes
- Off-by-one in finding the middle. The condition
while fast.next and fast.next.nextplacesslowat the end of the first half.while fast and fast.nextplacesslowone step further (start of second half for even-length lists). Pick one consistently and verify with examples. - Forgetting empty-list and single-node cases. Both are palindromes by convention. Early return prevents null-pointer issues in the rest of the algorithm.
- Not reversing the second half before comparing. Without reversal, you’re checking equality of two halves, which is a different property than palindrome.
- Modifying the input without restoring. If the interviewer cares about preserving the list, mutating it is a bug. Ask whether mutation is acceptable; restore if needed.
- Comparing pointers instead of values.
p1 == p2compares node identity, not value. Usep1.val != p2.valfor the palindrome check.
Frequently Asked Questions
What’s the expected interview answer?
Reverse second half in place, O(n) time and O(1) space. Walk through the three phases (find middle, reverse second half, compare). Mention the array approach as a warm-up if you have time. Strong candidates also mention restoring the list as a follow-up.
Why doesn’t the algorithm need to handle even and odd lengths separately?
The fast/slow pointer pattern naturally produces the right partition. For odd-length lists, the middle node is in the first half and gets skipped during comparison because the second half is one node shorter. For even-length lists, both halves are equal. The comparison loop terminates when the second-half pointer becomes None, handling both cases uniformly.
How is this different from the longest palindromic substring problem?
Palindromic substring works on strings (random-access, all substrings considered) and finds the longest. Palindrome linked list checks whether a fixed structure is a palindrome — it’s a yes/no problem on a single sequence. Different complexity, different approach. The “reverse half” trick is specific to linked lists; in strings, you’d use expand-around-center or DP.
What if the list is read-only or singly-linked with no extra mutation allowed?
Use the array-copy approach (O(n) space) or the recursive approach (O(n) stack space). Both achieve O(n) time without mutating. The O(1) space solution requires either reversal (mutation) or doubly-linked list (different structure). Strong candidates discuss this trade-off when asked.
How does this question generalize to other linked-list problems?
The fast/slow pointer pattern (find middle) is reusable for cycle detection (Floyd’s algorithm), finding the kth-from-end element, and rotating a list. The reverse helper is reusable for reverse-in-k-groups, reverse-between, and adding two numbers represented as lists. Mastering palindrome linked list often unlocks several other classic problems.
See also: Detect a Cycle in a Linked List • Reverse a Doubly Linked List • Longest Palindromic Substring
💡Strategies for Solving This Problem
The Problem
I got this question at Meta and initially froze. A palindrome linked list reads the same forwards and backwards: 1→2→2→1 is a palindrome, 1→2→3 isn't. The tricky part? You can't just index backwards like with an array.
Approach 1: Reverse and Compare (What I Used)
The insight: if the list is a palindrome, the first half equals the reversed second half.
- Find the middle using slow/fast pointers
- Reverse the second half
- Compare first half with reversed second half
My interviewer at Meta specifically said "You can modify the list." That's your hint to use this approach.
Approach 2: Using a Stack
Push first half onto a stack, then compare with second half as you pop. Simpler to implement but uses O(n) extra space. If interviewer says "optimize space," this won't fly.
Approach 3: Recursive (Elegant but Risky)
Use recursion to compare outer nodes working inward. Looks cool on paper but uses O(n) call stack space. I wouldn't use this in an interview unless you have time to kill.
Time Complexity
- Reverse approach: O(n) time, O(1) space - this is what they want
- Stack approach: O(n) time, O(n) space
- Recursive: O(n) time, O(n) space (call stack)
Pro tip: Start by asking "Can I modify the original list?" This tells you which approach to use.