Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Dummy Node, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

In-Place Linked List Reversal (Reverse Linked List II, Reverse Nodes in k-Group)

The problem

Reverse Linked List (LC 206)(opens leetcode.com)

You get head, the first node of a singly linked list. Each node holds a value val and a pointer next to the node after it; the last node's next is None, and an empty list has head = None.

Turn the list around: the node that was last must come first, and every node must point to the one that used to come before it. Return the first node of the reversed list (None for an empty list, written []).

Follow-up: once you have one version, write the other one too, a loop and a recursive function.

Example 1
Input:head = [1,2,3,4,5]
Output:[5,4,3,2,1]
Every link now points the other way, so 5, the old last node, is the new head and 1 ends the list.
12345

Linked List Reversal

head ➔ rewire each next pointer ➔ reversed list
Spot it when: You must reverse all or part of a singly linked list in place: the whole list, the nodes between two positions, every group of k nodes, or the second half before comparing or merging it with the first.
Linked List Reversal
Python
1from typing import Optional
2 
3 
4class ListNode:
5 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
6 self.val = val
7 self.next = next
8 
9 
10def reverse_list(head: Optional[ListNode]) -> Optional[ListNode]:
11 """Reverse Linked List: flip every next pointer in one pass.
12 Time: O(N) | Space: O(1)
13 """
14 prev = None # Head of the reversed part: empty at the start
15 curr = head # First node not flipped yet
16 while curr:
17 nxt = curr.next # Save the rest BEFORE the flip overwrites curr.next
18 curr.next = prev # Flip: curr now points back at the reversed part
19 prev = curr # The reversed part grows by one node
20 curr = nxt # Step into the saved rest
21 return prev # The old tail is the new head
22 
23 
24def reverse_between(head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
25 """Reverse Linked List II: reverse positions left..right (1-indexed) in one pass.
26 Time: O(N) | Space: O(1)
27 """
28 dummy = ListNode(0, head) # The segment may start at the head: give it a node before it
29 before = dummy
30 for _ in range(left - 1): # Walk to the node just before position left
31 before = before.next
32 prev = None
33 curr = before.next
34 for _ in range(right - left + 1): # The same flip, exactly right - left + 1 times
35 nxt = curr.next
36 curr.next = prev
37 prev = curr
38 curr = nxt
39 tail = before.next # The segment's old first node is now its last
40 before.next = prev # Reattach the reversed segment...
41 tail.next = curr # ...and hang the untouched rest behind it
42 return dummy.next
Invariant: prev heads the nodes already reversed and curr heads the nodes not touched yet: every node is in exactly one of the two lists.

Reversing a singly linked list only needs every next pointer to point the other way, so walk the list once with two pointers: prev heads the part already reversed (it starts as None) and curr is the first node not flipped yet; each step points curr.next back at prev, then moves prev and curr one node forward. Each step moves exactly one node from the front of the untouched part to the front of the reversed part, so no node is lost or visited twice, and when curr falls off the end, prev is the new head. reverse_between runs the same steps right - left + 1 times from the node before position left, then reconnects both ends with before.next = prev and tail.next = curr. The trap is the order of the lines: curr.next = prev overwrites the only link to the rest of the list, so save nxt = curr.next first; flip first and every node after curr is lost.

See it run

Linked list reversal, head = [1, 2, 3, 4, 5]

prev None · curr 1

prev None · curr 1

prev None · curr 1 · nxt 2

prev None · curr 1 · nxt 2

prev 1 · curr 2

prev 1 · curr 2 · nxt 3

prev 1 · curr 2 · nxt 3

prev 2 · curr 3

prev 2 · curr 3 · nxt 4

prev 2 · curr 3 · nxt 4

prev 3 · curr 4

prev 3 · curr 4 · nxt 5

prev 3 · curr 4 · nxt 5

prev 4 · curr 5

prev 4 · curr 5

prev 4 · curr 5

prev 5 · curr None

prev 5 · curr None · answer 5, 4, 3, 2, 1

Each circle is a node and each arrow is its next link. The arrows change direction one node at a time.

  • prev the head of the part that is already reversed
  • curr the node whose link is flipped now
  • nxt the saved rest of the list
  • new link the link that was just flipped
  • lost nodes that no variable and no link reaches any more

prev is None and curr is the head, node 1. Every link still points forward.

If curr.next = prev ran first, node 1 would point at None, and nxt = curr.next would also be None. Nodes 2, 3, 4, 5 could no longer be reached.

nxt = curr.next saves node 2 before any link changes.

curr.next = prev points node 1 at None. It becomes the tail.

prev = curr and curr = nxt move both pointers one node forward. prev is 1 and curr is 2.

nxt = curr.next saves node 3 before any link changes.

curr.next = prev points node 2 back at node 1.

prev = curr and curr = nxt move both pointers one node forward. prev is 2 and curr is 3.

nxt = curr.next saves node 4 before any link changes.

curr.next = prev points node 3 back at node 2.

prev = curr and curr = nxt move both pointers one node forward. prev is 3 and curr is 4.

nxt = curr.next saves node 5 before any link changes.

curr.next = prev points node 4 back at node 3.

prev = curr and curr = nxt move both pointers one node forward. prev is 4 and curr is 5.

nxt = curr.next saves None, because curr is the last node.

curr.next = prev points node 5 back at node 4.

prev = curr and curr = nxt move both pointers one node forward. prev is 5 and curr is None.

curr is None, so the loop stops. prev is the new head, node 5.

1curr2345
1 / 18
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing curr.next = prev before saving nxt

    Mistake
    while curr:
        curr.next = prev
        nxt = curr.next
    Fix
    while curr:
        nxt = curr.next
        curr.next = prev

    What happens: curr.next is the only link from curr to the rest of the list. curr.next = prev overwrites that link. Then nxt = curr.next saves prev, which is None on the first step. The loop ends after one node, and every node after curr is lost.

    Example: head = [1, 2, 3]
    Mistake: returns [1]Fix: returns [3, 2, 1]
  2. 2

    Gotcha 2: Returning head instead of prev

    Mistake
    return head
    Fix
    return prev

    What happens: When the loop ends, curr is None, and prev is the old last node. That node is the new head. The old head is now the tail, and its next is None. Returning it gives a list with one node.

    Example: head = [1, 2, 3]
    Mistake: returns [1]Fix: returns [3, 2, 1]
  3. 3

    Gotcha 3: Starting before at head instead of dummy

    Mistake
    before = head
    Fix
    before = dummy

    What happens: before must be the node just before position left. When left is 1, that is the node before the head. Only dummy can be there. Starting at head puts before one node too far, so the wrong segment is reversed.

    Example: head = [1, 2, 3, 4, 5], left = 1, right = 2
    Mistake: returns [1, 3, 2, 4, 5]Fix: returns [2, 1, 3, 4, 5]
  4. 4

    Gotcha 4: Flipping a last group shorter than k

    Mistake
    while node and count < k:
        node = node.next
        count += 1
    Fix
    while node and count < k:
        node = node.next
        count += 1
    if count < k:
        return dummy.next

    What happens: In Reverse Nodes in k-Group (LC 25), a last group with fewer than k nodes must keep its order. The count loop stops when the list ends, so count is less than k for that group. Without if count < k, the flip loop still reverses those count nodes. The last group comes back reversed.

    Example: head = [1, 2, 3, 4, 5], k = 3
    Mistake: returns [3, 2, 1, 5, 4]Fix: returns [3, 2, 1, 4, 5]
Not this one if…
  • The data is an array you can index, as in Rotate Array (LC 189): reverse by swapping values from both ends of a range, not by rewiring next pointers. See Three-Reversals Algorithm

Deep Dive

Why every node moves from one list to the other

A singly linked list stores one pointer per node, next. Reversing the list means that every next points the other way. The old tail becomes the head, and the old head points at None.

The loop keeps two lists. prev is the head of the part that is already reversed. It starts as None, which is an empty list. curr is the head of the part that is not touched yet. It starts as head. Every node is in exactly one of the two lists.

One round does four things. nxt = curr.next saves the rest of the untouched part. curr.next = prev makes curr point at the reversed part. prev = curr makes curr the new head of the reversed part. curr = nxt steps into the saved rest. So one round moves exactly one node from the front of the untouched list to the front of the reversed list.

Trace head = [1, 2, 3, 4, 5] from Reverse Linked List (LC 206). After round 1, the reversed list is 1 and the untouched list is 2, 3, 4, 5. After round 2, the reversed list is 2, 1. After round 5, the reversed list is 5, 4, 3, 2, 1 and curr is None, so the loop stops. prev is the new head.

Each node is moved once, and each round does a fixed number of steps. So the loop takes N rounds. An empty list never enters the loop, and return prev returns None.

Why nxt must be saved before the flip

curr.next is the only link from curr to the rest of the list. The line curr.next = prev overwrites that link. After it, no variable and no pointer reaches the nodes behind curr.

Look at the trap step on the board. Suppose the code runs curr.next = prev first. For the head 1, prev is None, so node 1 now points at None. Then nxt = curr.next saves None, not node 2. The loop sets curr = None and stops after one round. The nodes 2, 3, 4, 5 still point at each other, but nothing points at node 2. They are lost, and the function returns a list with one node.

The correct order is: save, flip, move prev, move curr. The save must come first because it reads the link that the flip destroys.

A second mistake is return head. When the loop ends, the old head is the tail. Its next is None, so returning it gives a list with one node. The new head is prev.

Reversing only the positions from left to right

Reverse Linked List II (LC 92) reverses the nodes from position left to position right and keeps the others. reverse_between runs the same four lines, but only right - left + 1 times, and then it reconnects both ends.

Trace head = [1, 2, 3, 4, 5] with left = 2 and right = 4. dummy is a node placed before the head. before starts at dummy and moves left - 1 = 1 time, so before is node 1. prev is None and curr is node 2.

Three rounds flip node 2, node 3 and node 4. Now prev is node 4, the head of the reversed part. curr is node 5, the first node after the segment. Node 2 points at None.

tail = before.next reads node 2, the first node of the segment, which is now its last node. before.next = prev makes node 1 point at node 4. tail.next = curr makes node 2 point at node 5. The result is [1, 4, 3, 2, 5].

tail must be read before before.next = prev runs, because that line overwrites the only link to node 2. dummy is needed for left = 1. Then no real node is before the segment, and dummy.next is what the function returns.

Cost and the problems that reuse the flip

Both functions take O(N) time, because each node is visited a constant number of times. They take O(1) space: three pointers and, in reverse_between, dummy, before and tail. A recursive version needs O(N) space for the call stack.

Palindrome Linked List (LC 234) finds the middle, reverses the second half in place and compares the two halves node by node. Reorder List (LC 143) also reverses the second half, and then it takes one node from each half in turn.

Reverse Nodes in k-Group (LC 25) uses the reverse_between steps for each group of k nodes. A counting loop checks first that k nodes remain. A last group with fewer than k nodes stays as it is. Without that check, the flip loop would reverse the short group as well.

If you can index the data, as with an array, do not rewire anything. Swap values from both ends instead, as the Three-Reversals Algorithm card does.

60-Second Pitch

"I reverse the list in place with two pointers. prev heads the part that is already reversed and starts as None. curr heads the untouched part and starts at head. Each round saves nxt = curr.next, points curr.next at prev, moves prev to curr and moves curr to nxt. The save comes first because the flip overwrites the only link to the rest of the list. Each round moves one node from the untouched list to the front of the reversed list. When curr is None, prev is the new head. For positions left to right, I use a dummy node, walk before to the node before left, run the same rounds right - left + 1 times, and reconnect with before.next = prev and tail.next = curr. The time is O(N) and the space is O(1)."

Common Questions

What is the time and space complexity?

O(N) time, because each node is flipped once. O(1) space, because the loop keeps only prev, curr and nxt. A recursive solution is also O(N) time but uses O(N) space for the call stack.

Why do you need a dummy node in reverse_between?

The segment can start at the head. Then no real node is before it, and before.next = prev would have nothing to change. dummy gives the head a node before it. The function returns dummy.next, which is the new head in every case.

Why not copy the values into an array, reverse it and write the values back?

It works, but it uses O(N) extra space. The statement of Reverse Linked List (LC 206) asks for the nodes to be rewired, and the follow-up asks for an iterative and a recursive version. Rewiring keeps the space at O(1).

How do you reverse a list in groups of k?

Count k nodes from before.next. If fewer than k remain, return. Otherwise run the flip loop k times, reconnect with before.next = prev and tail.next = curr, and set before = tail for the next group. This is Reverse Nodes in k-Group (LC 25).

How do you check that a linked list is a palindrome in O(1) space?

Find the middle, reverse the second half in place with the flip loop, and compare the two halves node by node. This is Palindrome Linked List (LC 234). If the list must stay unchanged, reverse the second half again after the comparison.

SWE Reasoning

Spot it

  • The input is a singly linked list, and the statement says reverse it, or reverse part of it, or reverse groups of k nodes.
  • The extra space must be O(1), so you cannot copy the values into an array and write them back.
  • The second half of a list must be compared or merged with the first half in reverse order (Palindrome Linked List (LC 234), Reorder List (LC 143)).
  • The statement gives two positions and asks to reverse the nodes between them (Reverse Linked List II (LC 92)).

The invariant, step by step

  1. Before the loop, prev is None and curr is head. The reversed list is empty. The untouched list is the whole list.
  2. nxt = curr.next saves the untouched list that follows curr.
  3. curr.next = prev makes curr point at the reversed list, so curr is now the head of a longer reversed list.
  4. prev = curr names that new head, and curr = nxt makes the saved list the untouched list.
  5. Every node is in exactly one of the two lists after every round, and the untouched list is one node shorter.
  6. When curr is None, the untouched list is empty. prev heads the whole reversed list, so return prev.

Traps

  • curr.next = prev before nxt = curr.next. The flip overwrites the only link to the rest of the list. For [1, 2, 3], the result is [1].
  • return head. The old head is the tail after the loop. Return prev.
  • before = head in reverse_between. When left is 1, the node before the segment is dummy. With [1, 2, 3, 4, 5], left = 1 and right = 2, the result is [1, 3, 2, 4, 5] instead of [2, 1, 3, 4, 5].
  • Reading tail after before.next = prev. tail would be node 4, the new head of the segment. Then tail.next = curr points node 4 at node 5, and nodes 3 and 2 are cut out of the list.
  • Reversing a last group shorter than k. In Reverse Nodes in k-Group (LC 25), count k nodes first. If fewer remain, stop without flipping.

Practice

Canonical LeetCode:Reverse Linked List (LC 206)Reverse Linked List II (LC 92)Palindrome Linked List (LC 234)Reorder List (LC 143)Reverse Nodes in k-Group (LC 25)
Theory Context•Two Pointers & Sliding Window

Builds on Dummy Node, which you haven’t finished yet.

P1MediumMental model~4 min speedrun

In-Place Linked List Reversal (Reverse Linked List II, Reverse Nodes in k-Group)

The problem

Reverse Linked List (LC 206)(opens leetcode.com)

You get head, the first node of a singly linked list. Each node holds a value val and a pointer next to the node after it; the last node's next is None, and an empty list has head = None.

Turn the list around: the node that was last must come first, and every node must point to the one that used to come before it. Return the first node of the reversed list (None for an empty list, written []).

Follow-up: once you have one version, write the other one too, a loop and a recursive function.

Example 1
Input:head = [1,2,3,4,5]
Output:[5,4,3,2,1]
Every link now points the other way, so 5, the old last node, is the new head and 1 ends the list.
12345

Linked List Reversal

head ➔ rewire each next pointer ➔ reversed list
Spot it when: You must reverse all or part of a singly linked list in place: the whole list, the nodes between two positions, every group of k nodes, or the second half before comparing or merging it with the first.
Linked List Reversal
Python
1from typing import Optional
2 
3 
4class ListNode:
5 def __init__(self, val: int = 0, next: Optional["ListNode"] = None):
6 self.val = val
7 self.next = next
8 
9 
10def reverse_list(head: Optional[ListNode]) -> Optional[ListNode]:
11 """Reverse Linked List: flip every next pointer in one pass.
12 Time: O(N) | Space: O(1)
13 """
14 prev = None # Head of the reversed part: empty at the start
15 curr = head # First node not flipped yet
16 while curr:
17 nxt = curr.next # Save the rest BEFORE the flip overwrites curr.next
18 curr.next = prev # Flip: curr now points back at the reversed part
19 prev = curr # The reversed part grows by one node
20 curr = nxt # Step into the saved rest
21 return prev # The old tail is the new head
22 
23 
24def reverse_between(head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
25 """Reverse Linked List II: reverse positions left..right (1-indexed) in one pass.
26 Time: O(N) | Space: O(1)
27 """
28 dummy = ListNode(0, head) # The segment may start at the head: give it a node before it
29 before = dummy
30 for _ in range(left - 1): # Walk to the node just before position left
31 before = before.next
32 prev = None
33 curr = before.next
34 for _ in range(right - left + 1): # The same flip, exactly right - left + 1 times
35 nxt = curr.next
36 curr.next = prev
37 prev = curr
38 curr = nxt
39 tail = before.next # The segment's old first node is now its last
40 before.next = prev # Reattach the reversed segment...
41 tail.next = curr # ...and hang the untouched rest behind it
42 return dummy.next
Invariant: prev heads the nodes already reversed and curr heads the nodes not touched yet: every node is in exactly one of the two lists.

Reversing a singly linked list only needs every next pointer to point the other way, so walk the list once with two pointers: prev heads the part already reversed (it starts as None) and curr is the first node not flipped yet; each step points curr.next back at prev, then moves prev and curr one node forward. Each step moves exactly one node from the front of the untouched part to the front of the reversed part, so no node is lost or visited twice, and when curr falls off the end, prev is the new head. reverse_between runs the same steps right - left + 1 times from the node before position left, then reconnects both ends with before.next = prev and tail.next = curr. The trap is the order of the lines: curr.next = prev overwrites the only link to the rest of the list, so save nxt = curr.next first; flip first and every node after curr is lost.

See it run

Linked list reversal, head = [1, 2, 3, 4, 5]

prev None · curr 1

prev None · curr 1

prev None · curr 1 · nxt 2

prev None · curr 1 · nxt 2

prev 1 · curr 2

prev 1 · curr 2 · nxt 3

prev 1 · curr 2 · nxt 3

prev 2 · curr 3

prev 2 · curr 3 · nxt 4

prev 2 · curr 3 · nxt 4

prev 3 · curr 4

prev 3 · curr 4 · nxt 5

prev 3 · curr 4 · nxt 5

prev 4 · curr 5

prev 4 · curr 5

prev 4 · curr 5

prev 5 · curr None

prev 5 · curr None · answer 5, 4, 3, 2, 1

Each circle is a node and each arrow is its next link. The arrows change direction one node at a time.

  • prev the head of the part that is already reversed
  • curr the node whose link is flipped now
  • nxt the saved rest of the list
  • new link the link that was just flipped
  • lost nodes that no variable and no link reaches any more

prev is None and curr is the head, node 1. Every link still points forward.

If curr.next = prev ran first, node 1 would point at None, and nxt = curr.next would also be None. Nodes 2, 3, 4, 5 could no longer be reached.

nxt = curr.next saves node 2 before any link changes.

curr.next = prev points node 1 at None. It becomes the tail.

prev = curr and curr = nxt move both pointers one node forward. prev is 1 and curr is 2.

nxt = curr.next saves node 3 before any link changes.

curr.next = prev points node 2 back at node 1.

prev = curr and curr = nxt move both pointers one node forward. prev is 2 and curr is 3.

nxt = curr.next saves node 4 before any link changes.

curr.next = prev points node 3 back at node 2.

prev = curr and curr = nxt move both pointers one node forward. prev is 3 and curr is 4.

nxt = curr.next saves node 5 before any link changes.

curr.next = prev points node 4 back at node 3.

prev = curr and curr = nxt move both pointers one node forward. prev is 4 and curr is 5.

nxt = curr.next saves None, because curr is the last node.

curr.next = prev points node 5 back at node 4.

prev = curr and curr = nxt move both pointers one node forward. prev is 5 and curr is None.

curr is None, so the loop stops. prev is the new head, node 5.

1curr2345
1 / 18
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Writing curr.next = prev before saving nxt

    Mistake
    while curr:
        curr.next = prev
        nxt = curr.next
    Fix
    while curr:
        nxt = curr.next
        curr.next = prev

    What happens: curr.next is the only link from curr to the rest of the list. curr.next = prev overwrites that link. Then nxt = curr.next saves prev, which is None on the first step. The loop ends after one node, and every node after curr is lost.

    Example: head = [1, 2, 3]
    Mistake: returns [1]Fix: returns [3, 2, 1]
  2. 2

    Gotcha 2: Returning head instead of prev

    Mistake
    return head
    Fix
    return prev

    What happens: When the loop ends, curr is None, and prev is the old last node. That node is the new head. The old head is now the tail, and its next is None. Returning it gives a list with one node.

    Example: head = [1, 2, 3]
    Mistake: returns [1]Fix: returns [3, 2, 1]
  3. 3

    Gotcha 3: Starting before at head instead of dummy

    Mistake
    before = head
    Fix
    before = dummy

    What happens: before must be the node just before position left. When left is 1, that is the node before the head. Only dummy can be there. Starting at head puts before one node too far, so the wrong segment is reversed.

    Example: head = [1, 2, 3, 4, 5], left = 1, right = 2
    Mistake: returns [1, 3, 2, 4, 5]Fix: returns [2, 1, 3, 4, 5]
  4. 4

    Gotcha 4: Flipping a last group shorter than k

    Mistake
    while node and count < k:
        node = node.next
        count += 1
    Fix
    while node and count < k:
        node = node.next
        count += 1
    if count < k:
        return dummy.next

    What happens: In Reverse Nodes in k-Group (LC 25), a last group with fewer than k nodes must keep its order. The count loop stops when the list ends, so count is less than k for that group. Without if count < k, the flip loop still reverses those count nodes. The last group comes back reversed.

    Example: head = [1, 2, 3, 4, 5], k = 3
    Mistake: returns [3, 2, 1, 5, 4]Fix: returns [3, 2, 1, 4, 5]
Not this one if…
  • The data is an array you can index, as in Rotate Array (LC 189): reverse by swapping values from both ends of a range, not by rewiring next pointers. See Three-Reversals Algorithm

Deep Dive

Why every node moves from one list to the other

A singly linked list stores one pointer per node, next. Reversing the list means that every next points the other way. The old tail becomes the head, and the old head points at None.

The loop keeps two lists. prev is the head of the part that is already reversed. It starts as None, which is an empty list. curr is the head of the part that is not touched yet. It starts as head. Every node is in exactly one of the two lists.

One round does four things. nxt = curr.next saves the rest of the untouched part. curr.next = prev makes curr point at the reversed part. prev = curr makes curr the new head of the reversed part. curr = nxt steps into the saved rest. So one round moves exactly one node from the front of the untouched list to the front of the reversed list.

Trace head = [1, 2, 3, 4, 5] from Reverse Linked List (LC 206). After round 1, the reversed list is 1 and the untouched list is 2, 3, 4, 5. After round 2, the reversed list is 2, 1. After round 5, the reversed list is 5, 4, 3, 2, 1 and curr is None, so the loop stops. prev is the new head.

Each node is moved once, and each round does a fixed number of steps. So the loop takes N rounds. An empty list never enters the loop, and return prev returns None.

Why nxt must be saved before the flip

curr.next is the only link from curr to the rest of the list. The line curr.next = prev overwrites that link. After it, no variable and no pointer reaches the nodes behind curr.

Look at the trap step on the board. Suppose the code runs curr.next = prev first. For the head 1, prev is None, so node 1 now points at None. Then nxt = curr.next saves None, not node 2. The loop sets curr = None and stops after one round. The nodes 2, 3, 4, 5 still point at each other, but nothing points at node 2. They are lost, and the function returns a list with one node.

The correct order is: save, flip, move prev, move curr. The save must come first because it reads the link that the flip destroys.

A second mistake is return head. When the loop ends, the old head is the tail. Its next is None, so returning it gives a list with one node. The new head is prev.

Reversing only the positions from left to right

Reverse Linked List II (LC 92) reverses the nodes from position left to position right and keeps the others. reverse_between runs the same four lines, but only right - left + 1 times, and then it reconnects both ends.

Trace head = [1, 2, 3, 4, 5] with left = 2 and right = 4. dummy is a node placed before the head. before starts at dummy and moves left - 1 = 1 time, so before is node 1. prev is None and curr is node 2.

Three rounds flip node 2, node 3 and node 4. Now prev is node 4, the head of the reversed part. curr is node 5, the first node after the segment. Node 2 points at None.

tail = before.next reads node 2, the first node of the segment, which is now its last node. before.next = prev makes node 1 point at node 4. tail.next = curr makes node 2 point at node 5. The result is [1, 4, 3, 2, 5].

tail must be read before before.next = prev runs, because that line overwrites the only link to node 2. dummy is needed for left = 1. Then no real node is before the segment, and dummy.next is what the function returns.

Cost and the problems that reuse the flip

Both functions take O(N) time, because each node is visited a constant number of times. They take O(1) space: three pointers and, in reverse_between, dummy, before and tail. A recursive version needs O(N) space for the call stack.

Palindrome Linked List (LC 234) finds the middle, reverses the second half in place and compares the two halves node by node. Reorder List (LC 143) also reverses the second half, and then it takes one node from each half in turn.

Reverse Nodes in k-Group (LC 25) uses the reverse_between steps for each group of k nodes. A counting loop checks first that k nodes remain. A last group with fewer than k nodes stays as it is. Without that check, the flip loop would reverse the short group as well.

If you can index the data, as with an array, do not rewire anything. Swap values from both ends instead, as the Three-Reversals Algorithm card does.

60-Second Pitch

"I reverse the list in place with two pointers. prev heads the part that is already reversed and starts as None. curr heads the untouched part and starts at head. Each round saves nxt = curr.next, points curr.next at prev, moves prev to curr and moves curr to nxt. The save comes first because the flip overwrites the only link to the rest of the list. Each round moves one node from the untouched list to the front of the reversed list. When curr is None, prev is the new head. For positions left to right, I use a dummy node, walk before to the node before left, run the same rounds right - left + 1 times, and reconnect with before.next = prev and tail.next = curr. The time is O(N) and the space is O(1)."

Common Questions

What is the time and space complexity?

O(N) time, because each node is flipped once. O(1) space, because the loop keeps only prev, curr and nxt. A recursive solution is also O(N) time but uses O(N) space for the call stack.

Why do you need a dummy node in reverse_between?

The segment can start at the head. Then no real node is before it, and before.next = prev would have nothing to change. dummy gives the head a node before it. The function returns dummy.next, which is the new head in every case.

Why not copy the values into an array, reverse it and write the values back?

It works, but it uses O(N) extra space. The statement of Reverse Linked List (LC 206) asks for the nodes to be rewired, and the follow-up asks for an iterative and a recursive version. Rewiring keeps the space at O(1).

How do you reverse a list in groups of k?

Count k nodes from before.next. If fewer than k remain, return. Otherwise run the flip loop k times, reconnect with before.next = prev and tail.next = curr, and set before = tail for the next group. This is Reverse Nodes in k-Group (LC 25).

How do you check that a linked list is a palindrome in O(1) space?

Find the middle, reverse the second half in place with the flip loop, and compare the two halves node by node. This is Palindrome Linked List (LC 234). If the list must stay unchanged, reverse the second half again after the comparison.

SWE Reasoning

Spot it

  • The input is a singly linked list, and the statement says reverse it, or reverse part of it, or reverse groups of k nodes.
  • The extra space must be O(1), so you cannot copy the values into an array and write them back.
  • The second half of a list must be compared or merged with the first half in reverse order (Palindrome Linked List (LC 234), Reorder List (LC 143)).
  • The statement gives two positions and asks to reverse the nodes between them (Reverse Linked List II (LC 92)).

The invariant, step by step

  1. Before the loop, prev is None and curr is head. The reversed list is empty. The untouched list is the whole list.
  2. nxt = curr.next saves the untouched list that follows curr.
  3. curr.next = prev makes curr point at the reversed list, so curr is now the head of a longer reversed list.
  4. prev = curr names that new head, and curr = nxt makes the saved list the untouched list.
  5. Every node is in exactly one of the two lists after every round, and the untouched list is one node shorter.
  6. When curr is None, the untouched list is empty. prev heads the whole reversed list, so return prev.

Traps

  • curr.next = prev before nxt = curr.next. The flip overwrites the only link to the rest of the list. For [1, 2, 3], the result is [1].
  • return head. The old head is the tail after the loop. Return prev.
  • before = head in reverse_between. When left is 1, the node before the segment is dummy. With [1, 2, 3, 4, 5], left = 1 and right = 2, the result is [1, 3, 2, 4, 5] instead of [2, 1, 3, 4, 5].
  • Reading tail after before.next = prev. tail would be node 4, the new head of the segment. Then tail.next = curr points node 4 at node 5, and nodes 3 and 2 are cut out of the list.
  • Reversing a last group shorter than k. In Reverse Nodes in k-Group (LC 25), count k nodes first. If fewer remain, stop without flipping.

Practice

Canonical LeetCode:Reverse Linked List (LC 206)Reverse Linked List II (LC 92)Palindrome Linked List (LC 234)Reorder List (LC 143)Reverse Nodes in k-Group (LC 25)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•