Check If a Linked List Is a Palindrome: Stack, Reverse-Half, Recursive

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 → True
  • 1 → 2 → False
  • 1 → 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 p2 is 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.next places slow at the end of the first half. while fast and fast.next places slow one 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 == p2 compares node identity, not value. Use p1.val != p2.val for 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 ListReverse a Doubly Linked ListLongest 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.

Scroll to Top