Problem Overview — Check if a Linked List is a Palindrome
LeetCode 234 Palindrome Linked List gives you the head of a singly linked list and asks you to determine whether the list is a palindrome — that is, whether the sequence of node values reads the same forward and backward. Return true if it is a palindrome, false otherwise.
The straightforward follow-up that interviewers always ask: can you solve it in O(n) time and O(1) extra space? Copying values to an array is easy but uses O(n) space. The O(1) space solution requires mutating the list itself, which means understanding three core linked list primitives and composing them carefully.
Knowing the constraints helps size your approach. The list length can reach 100,000 nodes, so any O(n²) approach like comparing each node against its mirror by traversal will time out. The key insight is that you do not need random access — you only need to walk both halves simultaneously once the list is set up correctly.
- The number of nodes in the list is in the range [1, 100000]
- 0 <= Node.val <= 9
- Node values are single digits, making it a sequence of digits to palindrome-check
- Return true if the linked list is a palindrome, false otherwise
- Follow-up: Could you do it in O(n) time and O(1) space?
O(n) Space Approaches — Array Copy and Recursion
The simplest O(n) space approach copies all node values into an array or list, then uses a two-pointer technique on the array: one pointer starting from index 0 and one from the last index, walking inward and comparing values. If any pair mismatches, return false. If all pairs match, return true. This is clean, easy to reason about, and perfectly correct — but uses O(n) extra space for the array.
A second O(n) space approach uses the call stack recursively. You can write a recursive function that walks to the end of the list, then compares values on the way back out. The recursion depth equals the list length, so in the worst case the call stack holds O(n) frames. While elegant, this approach risks a stack overflow for lists of 100,000 nodes in languages without tail-call optimization.
Both approaches are worth mentioning in an interview to show breadth, but the O(1) space solution is what the problem is really testing. The O(1) approach is also the most instructive — it reveals the underlying structure of three fundamental linked list operations working together.
The O(1) Space Trick Combines Three Linked List Fundamentals
The O(1) space palindrome check is a composition of three classic linked list operations: (1) find the middle node using fast and slow pointers, (2) reverse the second half of the list in place using iterative pointer reversal, and (3) compare the first half and reversed second half node by node. Mastering this problem means mastering all three primitives at once.
Step 1: Find the Middle with Fast and Slow Pointers
Use two pointers: slow starts at head and moves one node at a time, fast starts at head and moves two nodes at a time. When fast reaches the end of the list (fast is None or fast.next is None), slow is pointing at the middle node. This is the classic tortoise-and-hare middle-finding technique.
For an odd-length list of length 2k+1, slow will stop at the exact center node — index k (0-indexed). For an even-length list of length 2k, slow will stop at the second of the two middle nodes — index k. In both cases, the second half of the list begins at slow.next, which is where we start the reversal.
The stopping condition matters. When fast reaches the last node (fast.next is None), slow is at the middle. When fast steps off the end (fast is None), slow has advanced one extra step. The correct stopping condition is to stop before fast steps off the end so that slow lands at the last node of the first half (for even length) or the exact middle (for odd length).
- 1Initialize slow = head, fast = head
- 2While fast is not None and fast.next is not None: slow = slow.next; fast = fast.next.next
- 3When the loop ends, slow points to the middle node
- 4For odd-length lists: slow is the exact center node (not compared in palindrome check)
- 5For even-length lists: slow is the last node of the first half
- 6The second half begins at slow.next — this is the head for the reversal step
Step 2: Reverse the Second Half In Place
Starting from slow.next (the head of the second half), reverse the second half of the linked list using the standard iterative reversal technique. Use three pointers: prev initialized to None, curr initialized to slow.next, and next_node as a temporary pointer. In each iteration, save curr.next to next_node, point curr.next back to prev, advance prev to curr, and advance curr to next_node. When curr becomes None, prev is the new head of the reversed second half.
After reversal, you have two independent sub-lists: the first half starting at head (ending at slow, which still points to the last node of the first half) and the reversed second half starting at prev (the new tail of the reversed half points to None). Both halves can now be traversed from their respective heads for comparison.
It is important to disconnect the first half from the second half by setting slow.next = None before (or after) the reversal. This prevents the comparison loop from accidentally following stale pointers. Some implementations set slow.next = None as a cleanup step; others rely on the None terminator of the reversed second half to stop the comparison loop naturally.
Odd-Length Lists: The Middle Node Does Not Affect the Comparison
For odd-length lists the slow pointer lands on the exact center node. After reversal the reversed second half is one node shorter than the first half. The comparison loop terminates when the reversed half is exhausted — the middle node in the first half is simply never compared. This is correct: a palindrome's center node always matches itself, so skipping it produces no false results.
Step 3: Compare and Optionally Restore the List
Walk both halves simultaneously: pointer left starts at head, pointer right starts at prev (the head of the reversed second half). At each step compare left.val and right.val. If they ever differ, the list is not a palindrome — record false. Advance both pointers by one. The loop ends when right becomes None (the reversed second half is exhausted).
If all compared values matched, the list is a palindrome. Return true. If any mismatch was found, return false. The comparison is inherently O(n/2) — roughly half the list length — but simplifies to O(n) in asymptotic notation.
If the problem or interview requires the list to be left unmodified after the check, reverse the second half again before returning. Reversing it a second time restores the original order. This is a non-destructive variant: reverse second half, compare, reverse back, return result. The extra reversal is O(n/2) and does not change the overall O(n) time complexity.
- 1Set left = head, right = prev (head of reversed second half)
- 2While right is not None: compare left.val and right.val
- 3If left.val != right.val, mark result = False
- 4Advance left = left.next, right = right.next
- 5After loop: result holds the palindrome verdict
- 6Optionally: reverse the second half again to restore the original list
- 7Return result (True if palindrome, False otherwise)
Code Walkthrough — Python and Java Solutions
Python solution: define isPalindrome(head) with three helpers or inline logic. Find middle: slow = fast = head; while fast and fast.next: slow = slow.next; fast = fast.next.next. Reverse second half: prev = None; curr = slow.next; while curr: nxt = curr.next; curr.next = prev; prev = curr; curr = nxt. Compare: left, right = head, prev; result = True; while right: if left.val != right.val: result = False; left = left.next; right = right.next. Return result. O(n) time, O(1) space.
Java solution: public boolean isPalindrome(ListNode head). Find middle: ListNode slow = head, fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }. Reverse: ListNode prev = null, curr = slow.next; while (curr != null) { ListNode next = curr.next; curr.next = prev; prev = curr; curr = next; }. Compare: ListNode left = head, right = prev; while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true. O(n) time, O(1) space.
Both solutions use identical logic structured as three sequential phases. The Java solution short-circuits on the first mismatch by returning false immediately inside the comparison loop, which can save work for long non-palindromes. The Python version collects a boolean result and returns it after the loop — functionally equivalent but slightly less efficient for early failures. Both are accepted as optimal O(n)/O(1) solutions on LeetCode.
Restore the List if Non-Destructive Checking Is Required
Reversing the second half mutates the list in place. If the caller expects the list to remain intact after the palindrome check — or if the interview explicitly states the list must not be modified — you must reverse the second half a second time before returning. Skipping the restore step leaves the list in a broken state where the second half points backward, which can corrupt any code that traverses the list after your function returns.