Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Use two pointers, slow and fast, both starting at head. Advance slow by one and fast by two each iteration. When fast falls off the end, slow is sitting exactly on the middle. Because the list length is unknown up front, this finds the middle in a single pass with O(1) extra space — no length count needed. For even-length lists this returns the second of the two middle nodes (LeetCode 876's requirement).
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you know the fast/slow two-pointer trick to find a midpoint in one pass without first counting the length, and whether you handle the loop guard so you never dereference a null fast pointer.
Where candidates slip
Using `while fast.next and fast.next.next` (returns the first middle) when the problem asks for the second, or writing `while fast` alone and crashing on `fast.next.next` for even-length lists.
How to stand out
Clarify which middle is expected for even lengths before coding, state that starting both pointers at head yields the second middle, and connect the tortoise/hare engine to cycle detection and palindrome checking.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewFind the middle of a linked listIs the length known up front?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Count-Then-Walk — Two Passes
1Hint 1Explore hintHide hint
If you knew the length n, the middle index is just n / 2 (integer division) for the second-middle convention. How do you get n from a list?
2Hint 2Explore hintHide hint
Walk the list once counting nodes to get n. Then walk again from head, stopping after n / 2 steps.
3Hint 3Explore hintHide hint
Two passes, both O(n), O(1) space. It works, but you traverse most of the list twice — the fast/slow trick does it in one pass.
A · Core idea
The most direct reading of the problem: the middle is at index n / 2, so figure out n, then walk there. It splits naturally into two passes.
Why two passes are needed here. Arrays expose their length for free, but a singly linked list does not — the only way to learn n is to walk to the end counting nodes. Once we have n, the second-middle index for the problem's convention is n / 2 using integer division: for n = 5 that's index 2 (node 3), and for n = 6 that's index 3 (node 4), exactly the 'second middle' rule.
Why `n / 2` gives the second middle. With zero-based indexing, an even list of length n has middles at indices n/2 - 1 and n/2. Integer-dividing n / 2 lands on the larger of the two — the second middle. For odd n, n / 2 floors to the unique centre. So a single formula covers both parities, which is worth saying out loud.
Why we don't ship this. It's correct and still O(n), but it traverses roughly 1.5n nodes and reads the list twice. Interviewers use this problem specifically to see whether you know the single-pass fast/slow technique, so this serves only as the springboard.
When it is actually preferable. If you must find the middle repeatedly and can cache the length, or if you need the index for other reasons anyway, the explicit count is sometimes clearer. But as a standalone answer, lead with fast/slow.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1, 2, 3, 4, 5, 6]pass 1
count nodes 1..6
n = 6[4, 5, 6]C · Code with teaching comments
Reference implementation previewpublic class ListNode { int val; ListNode next; ListNode() {} ListNode(int val) { this.val = val; }Explore the full codeHide the full code
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The n / 2 integer-division formula quietly encodes the 'second middle on even length' rule for free — recognising that one expression handles both parities is the takeaway from this baseline.
ComplexityO(n) time, O(1) space — First pass counts all n nodes; second pass walks n/2 nodes. Total ~1.5n steps = O(n) time, O(1) space (just a counter and a pointer).
E · Edge cases and pitfalls5head = [1] — n = 1, n/2 = 0 → zero advances, return head. Single node is its own middle.Explore edge casesHide edge cases
Edge cases handled
head = [1]n = 1, n/2 = 0 → zero advances, return head. Single node is its own middle.head = [1, 2]n = 2, n/2 = 1 → one advance, return node 2 (the second middle).
Pitfalls
- Using (n - 1) / 2 — that targets the FIRST middle, the wrong one for this problem.
- Re-walking from the wrong start or off-by-one in the second loop.
- Presenting this as the final answer instead of advancing to fast/slow.
Approach 2 of 2 · Optimal
Fast/Slow Pointers — Single Pass (Optimal)
1Hint 1Explore hintHide hint
Imagine two runners on the list, one twice as fast as the other. When the fast one reaches the end, where is the slow one?
2Hint 2Explore hintHide hint
Start slow and fast at head. Each step: slow moves one node, fast moves two. The 2:1 ratio means slow covers half the distance fast does.
3Hint 3Explore hintHide hint
Loop while fast != null AND fast.next != null so fast.next.next is safe. When the loop ends, slow is on the middle — the second middle for even lengths.
A · Core idea
Two pointers leave the head together, but the fast one takes two steps for every one the slow one takes. By the time the fast pointer runs off the end of the list, the slow pointer — having moved at half the speed — is exactly halfway, which is the middle.
Why the 2:1 ratio lands slow on the middle. Distance is speed × time. Over the same number of iterations, fast travels twice as far as slow. When fast has traveled the full length n (reaching the end), slow has traveled n / 2 — the middle index. There's no arithmetic to do and no length to compute; the relative speed is the computation. This is why it's one pass instead of the count-then-walk's one-and-a-half.
Why the loop guard is `fast and fast.next`. Each iteration does fast = fast.next.next, which dereferences two links ahead. For that to be safe, both fast and fast.next must be non-null. Checking only fast crashes on even-length lists: when fast lands on the last node, fast.next is null and fast.next.next throws. Checking both stops the loop one step earlier and keeps the dereference legal.
Why this returns the second middle on even lengths. Trace [1,2,3,4,5,6]. Start: slow=1, fast=1. Iter1: slow=2, fast=3. Iter2: slow=3, fast=5. Iter3: slow=4, fast=null (5.next.next). Loop ends with slow=4 — the second of the two middles (3 and 4). Because both pointers begin at head, the geometry naturally favours the later middle, which is precisely what LeetCode 876 demands. If the problem wanted the first middle, you'd start fast one node ahead or loop while fast.next and fast.next.next.
Why for odd lengths it lands on the single centre. Trace [1,2,3,4,5]. Iter1: slow=2, fast=3. Iter2: slow=3, fast=5. Now fast.next is null, loop ends, slow=3 — the unique centre. The same code handles both parities with no branching.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
head = [1, 2, 3, 4, 5, 6]Array state
start
slow=1, fast=1
[4, 5, 6]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The relative speed between two pointers encodes a computation for free: a 2:1 ratio computes 'halfway', which is why no length count is needed. This insight generalises — a 1-step gap of k finds the kth-from-end (Remove Nth Node), and equal speed on a cycle guarantees a meeting (Linked List Cycle). One engine, many midpoint/cycle problems.
ComplexityO(n) time, O(1) space — fast traverses the whole list once (≈ n/2 iterations, each advancing fast by two), and slow advances by one per iteration. One pass total → O(n) time, O(1) space (two pointers).
E · Edge cases and pitfalls7head = [1] — fast=head, fast.next is null → loop never runs, return slow=head. A single node is its own middle.Explore edge casesHide edge cases
Edge cases handled
head = [1]fast=head, fast.next is null → loop never runs, return slow=head. A single node is its own middle.head = [1, 2]Iter1: slow→2, fast→null (2.next.next). Loop ends, return node 2 — the second middle.head = [1, 2, 3]Iter1: slow→2, fast→3. fast.next null → stop. Return node 2, the unique centre of an odd list.
Pitfalls
- Writing `while (fast != null)` only — crashes on `fast.next.next` for even-length lists.
- Starting fast at head.next when the problem wants the second middle — that yields the first middle.
- Advancing slow by two or fast by one by mistake — breaks the 2:1 ratio that defines the midpoint.
- Returning slow.val when the problem asks for the node.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic ListNode middleNode(ListNode head) { ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Missing the fast.next null check
On even-length lists fast eventually lands on the last node, where fast.next is null. Evaluating fast.next.next then dereferences null and crashes.
Explore the code comparisonHide the code comparison
Missing the fast.next null check
On even-length lists fast eventually lands on the last node, where fast.next is null. Evaluating fast.next.next then dereferences null and crashes.
Don't write this
while (fast != null) {
slow = slow.next;
fast = fast.next.next; // NPE when fast is the last node
}Write this instead
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}2Returning the first middle instead of the second
LeetCode 876 requires the second middle on even lengths. Looping on fast.next/fast.next.next or starting fast ahead lands slow on the first middle.
Explore the code comparisonHide the code comparison
Returning the first middle instead of the second
LeetCode 876 requires the second middle on even lengths. Looping on fast.next/fast.next.next or starting fast ahead lands slow on the first middle.
Don't write this
slow = head
fast = head
while fast.next and fast.next.next: # stops one step earlier
slow = slow.next
fast = fast.next.next
return slow # this is the FIRST middleWrite this instead
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # second middle, as required3Breaking the 2:1 speed ratio
The midpoint result depends entirely on fast moving exactly twice as fast as slow. Advancing fast by one (or slow by two) destroys the invariant.
Explore the code comparisonHide the code comparison
Breaking the 2:1 speed ratio
The midpoint result depends entirely on fast moving exactly twice as fast as slow. Advancing fast by one (or slow by two) destroys the invariant.
Don't write this
while fast and fast.next:
slow = slow.next
fast = fast.next # only one step — fast is no longer 'fast'
return slowWrite this instead
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slowRevise
Carry the pattern into the interview
Pattern to remember
Fast/slow two-pointer (tortoise and hare) midpoint
Code pattern to memorize
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # second middle when length is evenRules to remember
- 1Both pointers start at head; fast moves twice as fast as slow, so slow lands at the midpoint when fast finishes.
- 2Loop condition is
fast and fast.next— checking both prevents a null dereference onfast.next.next. - 3Starting both at
headreturns the second middle on even lengths (1→2→3→4→5→6 returns 4). LeetCode 876 wants exactly this. - 4To get the first middle instead, start fast at
head.next, or loop whilefast.next and fast.next.next. - 5No length counting needed — the 2:1 speed ratio finds the middle in one pass, which is the whole point over the count-then-walk approach.
- 6This same slow/fast skeleton detects cycles and finds the kth-from-end — internalise the pointer-advance order.
Use this pattern when
- Find the midpoint of a singly linked list without knowing its length
- Split a list into two halves (merge sort on lists, reorder, palindrome check)
- Any 'meet in the middle' or 'second-half' linked-list subproblem
- Cycle detection or kth-from-end — same tortoise/hare machinery
Choose another pattern when
- You already know the length (e.g. an array, or a length field) → just index to n/2
- It's a doubly linked list with a tail pointer → walk inward from both ends
- You need every node's index, not just the middle → a single counting pass is simpler
Interview takeaway
Fast/slow pointers turn 'find the middle without a length' into a single O(1)-space pass. The same two-pointer engine powers Linked List Cycle, Palindrome Linked List, and Reorder List — learn the advance order and the `fast and fast.next` guard once.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical fast/slow two-pointer midpoint. The 2:1-speed trick finds a midpoint in one O(1)-space pass and is the splitting step inside Palindrome Linked List, Reorder List, and merge sort on lists. The same engine with a different gap or equal speed solves Remove Nth Node and Linked List Cycle.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Palindrome Linked ListFind the middle with fast/slow, reverse the second half, then compare the two halves.
- Linked List CycleComing soonSame tortoise/hare engine; if fast ever meets slow there is a cycle.
- Remove Nth Node From End of ListComing soonTwo pointers with a fixed gap of n instead of a 2:1 ratio finds the kth-from-end in one pass.
- Reorder ListSplit at the middle (fast/slow), reverse the second half, then interleave.