Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 7 – Linked lists

Reversing linked lists and sublists

Reverse a singly linked list in place with three pointers, iteratively and recursively, then reverse a sublist between two positions and groups of k.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 9 of 30 minutes free
Free: Three references, one pass · Sublists: reverse the middle, reconnect both ends · Practise: groups of k. With a pass: 3 in-depth parts and 3 interview answers, about 21 minutes.

What you will learn

  • Implement list reversal iteratively and recursively
  • Implement the reversal of a sublist between two positions
  • Implement the reversal of nodes in groups of k
  • Check whether a list reads the same both ways and restore it afterwards

Before you start

Reversing a singly linked list means turning every next reference around, so that the old last node becomes the head and the old head points at nothing. It takes one pass and three references: prev, the start of the part already reversed; curr, the node being turned around; and nxt, saved before the turn because it is the only way to the rest. That costs O(n) time and O(1) extra memory, and no node is copied or moved. A recursive version does the same in fewer lines but keeps one call per node waiting, so it fails on long lists in Python. Sublists use the same step with two reconnections at their ends. Reversing linked lists sounds like a puzzle, yet the step recurs: in groups of k, in palindrome checks and in merge routines. This lesson traces the step on four nodes, then applies it to sublists.

Three references, one pass

Each pass of the loop does four things, always in this order: save the rest with nxt = curr.next, turn the current node around with curr.next = prev, move the start of the reversed part with prev = curr, and step on with curr = nxt. Swap the first two and the rest of the list is lost after one node. When curr becomes None, every node has been turned, and prev is the new head.

Halfway through reversing 1, 2, 3, 4: prev holds the reversed part 2, 1 and curr the untouched part 3, 4.reversed partuntouched partprevcurr21None34Nonenextnextnextnextnext step: 3.next = prev

The two halves of a reversal after two steps

Text description of the diagram

The list 1, 2, 3, 4 is being reversed in place, and two steps are done.

The reference prev points at the reversed part: node 2, whose next is node 1, whose next is None. The reference curr points at the untouched part: node 3, whose next is node 4, whose next is None. No node belongs to both parts, and no node is missing.

The dashed arrow shows the next step: after saving node 3's next, node 4, the step points node 3 back at node 2. Then prev moves to node 3 and curr to node 4, so the reversed part grows by one node and the untouched part shrinks by one.

The program below reverses 1 → 2 → 3 → 4 and prints both parts after every pass:

Every pointer change of a reversal Python · trace/reverse_trace.py
"""Reversing 1 -> 2 -> 3 -> 4 in place with three references, printing every pointer change."""


class Node:
    __slots__ = ("value", "next")

    def __init__(self, value, next=None):
        self.value = value
        self.next = next


def show(node):
    out = []
    while node is not None:
        out.append(str(node.value))
        node = node.next
    return " -> ".join(out + ["None"])


head = None
for v in (4, 3, 2, 1):
    head = Node(v, head)
print("start:", show(head))

prev, curr = None, head
step = 0
while curr is not None:
    step += 1
    nxt = curr.next  # 1. save the rest of the list
    curr.next = prev  # 2. turn this node around
    prev = curr  # 3. the reversed part now starts here
    curr = nxt  # 4. move on to the untouched part
    print(f"step {step}: turned {prev.value} around | reversed: {show(prev)} | untouched: {show(curr)}")
head = prev
print("result:", show(head))

Output

start: 1 -> 2 -> 3 -> 4 -> None
step 1: turned 1 around | reversed: 1 -> None | untouched: 2 -> 3 -> 4 -> None
step 2: turned 2 around | reversed: 2 -> 1 -> None | untouched: 3 -> 4 -> None
step 3: turned 3 around | reversed: 3 -> 2 -> 1 -> None | untouched: 4 -> None
step 4: turned 4 around | reversed: 4 -> 3 -> 2 -> 1 -> None | untouched: None
result: 4 -> 3 -> 2 -> 1 -> None

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 reverse_trace.py

Read the two parts column by column: the reversed part grows by one node per step and the untouched part shrinks by one, and together they always hold all four nodes. That observation is the loop’s invariant. Every node’s next is written exactly once, so the work is n assignments of next for n nodes, the least any reversal can do.

Reversing a singly linked list of n nodes
Operation Time Extra space Note
The three-pointer loop O(n) O(1) one pass, nothing copied
The recursive version O(n) O(n) one waiting call per node
A sublist of m nodes at position left O(left + m) O(1) walk to it, reverse it
Groups of k O(n) O(1) each node is counted once and turned once

The recursive version reverses the rest of the list first and then hangs the current node behind its old successor. All n calls wait at once, and CPython stops a recursion at sys.getrecursionlimit(), 1,000 frames by default, with a RecursionError. The recursion lesson covers those limits; for reversing linked lists of unknown length, use the loop.

Sublists: reverse the middle, reconnect both ends

To reverse only the nodes at positions left to right, walk to the node just before the sublist, reverse exactly right − left + 1 nodes with the same step, and reconnect both ends. The node before the sublist must point at the old last node of the sublist, and the old first node, now the last, must point at whatever followed the sublist. A dummy node in front of the head gives a sublist that starts at position 1 a node before it too, so no branch is needed.

Reversing positions left to right Python · sublist/reverse_between.py
"""Reverse only the nodes at positions left to right (counted from 1), with a dummy node in front of the head."""


class Node:
    __slots__ = ("value", "next")

    def __init__(self, value, next=None):
        self.value = value
        self.next = next


def build(values):
    head = None
    for v in reversed(values):
        head = Node(v, head)
    return head


def values_of(node):
    out = []
    while node is not None:
        out.append(node.value)
        node = node.next
    return out


def reverse_between(head, left, right):
    dummy = Node(None, head)
    before = dummy
    for _ in range(left - 1):  # 1. stop on the node before the sublist
        before = before.next
    first = before.next  # this node will end the reversed sublist
    prev, curr = None, first
    for _ in range(right - left + 1):  # 2. reverse exactly right - left + 1 nodes
        nxt = curr.next
        curr.next = prev
        prev, curr = curr, nxt
    before.next = prev  # 3. reconnect the front: before -> the old last node of the sublist
    first.next = curr  # 4. reconnect the back: the old first node -> the rest of the list
    return dummy.next


for values, left, right in [([1, 2, 3, 4, 5, 6], 2, 5), ([1, 2, 3, 4, 5, 6], 1, 3), ([1, 2, 3, 4, 5, 6], 4, 6), ([7, 8], 1, 2), ([9], 1, 1)]:
    result = values_of(reverse_between(build(values), left, right))
    print(f"reverse positions {left}..{right} of {values}: {result}")

Output

reverse positions 2..5 of [1, 2, 3, 4, 5, 6]: [1, 5, 4, 3, 2, 6]
reverse positions 1..3 of [1, 2, 3, 4, 5, 6]: [3, 2, 1, 4, 5, 6]
reverse positions 4..6 of [1, 2, 3, 4, 5, 6]: [1, 2, 3, 6, 5, 4]
reverse positions 1..2 of [7, 8]: [8, 7]
reverse positions 1..1 of [9]: [9]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 reverse_between.py

Five cases cover the positions that break carelessly written code: a sublist in the middle, at the front, at the end, the whole list, and a single node. Groups of k are sublists one after another: count k nodes first, reverse them, reconnect, and move on, leaving a final group shorter than k as it is unless the specification says otherwise. The same building block serves palindrome checks: locate the midpoint with a slow and a fast pointer, turn the back half around, compare the halves node by node, then turn the back half around again.

Practise: groups of k

Reverse a list in groups of k, keeping a short final group in its order. The sample tests check every k from 1 to 9 on random lists against a model, and that you relink the nodes you were given rather than building new ones.

Exercise · Medium · Python, JavaScript

Reverse a linked list in groups of k nodes

Write reverse_k_group(head, k) (in JavaScript reverseKGroup(head, k)): reverse the nodes of a singly linked list in groups of k, from the front, and return the new head. A final group with fewer than k nodes keeps its order. Relink the nodes you are given; do not create new ones or change their values. k is at least 1.

  • 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 with k = 3 becomes 3 → 2 → 1 → 6 → 5 → 4 → 7 → 8.
  • With k = 1 the list is unchanged, and with k equal to the length it is fully reversed.
  • An empty list (None, in JavaScript null) stays empty.

The nodes are ListNode objects with val and next, defined in the starter file. The sample tests check the examples, every k from 1 to 9 on lists of up to 30 nodes against a model, and that the nodes you return are the nodes you were given.

Python · Starter code · reverse_groups.py

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next


def reverse_k_group(head, k):
    """Reverse the nodes in groups of k from the front; a final shorter group keeps its order. Returns the new head."""
    return head
The sample tests · test_reverse_groups.py
import random

from reverse_groups import ListNode, reverse_k_group


def build(values):
    head = None
    for v in reversed(values):
        head = ListNode(v, head)
    return head


def nodes_of(head):
    out = []
    while head is not None:
        out.append(head)
        head = head.next
        assert len(out) <= 1_000, "the nodes loop back on themselves"
    return out


def values_of(head):
    return [n.val for n in nodes_of(head)]


def model(values, k):
    """Plainly right: reverse each full chunk of k values; a shorter last chunk keeps its order."""
    out = []
    for i in range(0, len(values), k):
        chunk = values[i : i + k]
        out += chunk[::-1] if len(chunk) == k else chunk
    return out


def test_groups_of_three():
    """the example: eight nodes in groups of 3"""
    assert values_of(reverse_k_group(build([1, 2, 3, 4, 5, 6, 7, 8]), 3)) == [3, 2, 1, 6, 5, 4, 7, 8]


def test_k_one_and_k_length():
    """k = 1 changes nothing, k = the length reverses everything"""
    assert values_of(reverse_k_group(build([1, 2, 3, 4]), 1)) == [1, 2, 3, 4]
    assert values_of(reverse_k_group(build([1, 2, 3, 4]), 4)) == [4, 3, 2, 1]


def test_k_longer_than_the_list():
    """a list shorter than k keeps its order"""
    assert values_of(reverse_k_group(build([1, 2, 3]), 5)) == [1, 2, 3]


def test_empty_and_single():
    """an empty list stays empty, and a single node stays"""
    assert reverse_k_group(None, 2) is None
    assert values_of(reverse_k_group(build([7]), 2)) == [7]


def test_exact_multiple():
    """six nodes in groups of 2: every group is complete"""
    assert values_of(reverse_k_group(build([1, 2, 3, 4, 5, 6]), 2)) == [2, 1, 4, 3, 6, 5]


def test_nodes_are_reused():
    """the result holds exactly the nodes that were given, each once, with their values unchanged"""
    head = build(list(range(10)))
    given = {id(n): n.val for n in nodes_of(head)}
    result = nodes_of(reverse_k_group(head, 3))
    assert len(result) == 10
    assert {id(n) for n in result} == set(given)
    assert all(given[id(n)] == n.val for n in result)


def test_against_the_model():
    """every k from 1 to 9 on random lists of 0 to 30 nodes"""
    rng = random.Random(9)
    for _ in range(300):
        values = [rng.randint(0, 99) for _ in range(rng.randint(0, 30))]
        for k in range(1, 10):
            assert values_of(reverse_k_group(build(values), k)) == model(values, k), (values, k)

JavaScript · Starter code · reverse_groups.mjs

export class ListNode {
  constructor(val, next = null) {
    this.val = val;
    this.next = next;
  }
}

/** Reverse the nodes in groups of k from the front; a final shorter group keeps its order. Returns the new head. */
export function reverseKGroup(head, k) {
  return head;
}
The sample tests · reverse_groups.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { ListNode, reverseKGroup } from './reverse_groups.mjs';

function build(values) {
  let head = null;
  for (let i = values.length - 1; i >= 0; i--) head = new ListNode(values[i], head);
  return head;
}

function nodesOf(head) {
  const out = [];
  for (let n = head; n !== null && n !== undefined; n = n.next) {
    out.push(n);
    if (out.length > 1000) throw new Error('the nodes loop back on themselves');
  }
  return out;
}

const valuesOf = (head) => nodesOf(head).map((n) => n.val);

/** Plainly right: reverse each full chunk of k values; a shorter last chunk keeps its order. */
function model(values, k) {
  const out = [];
  for (let i = 0; i < values.length; i += k) {
    const chunk = values.slice(i, i + k);
    out.push(...(chunk.length === k ? chunk.reverse() : chunk));
  }
  return out;
}

let seed = 9;
const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
const randint = (lo, hi) => lo + Math.floor(random() * (hi - lo + 1));

test('the example: eight nodes in groups of 3', () => {
  assert.deepEqual(valuesOf(reverseKGroup(build([1, 2, 3, 4, 5, 6, 7, 8]), 3)), [3, 2, 1, 6, 5, 4, 7, 8]);
});

test('k = 1 changes nothing, k = the length reverses everything', () => {
  assert.deepEqual(valuesOf(reverseKGroup(build([1, 2, 3, 4]), 1)), [1, 2, 3, 4]);
  assert.deepEqual(valuesOf(reverseKGroup(build([1, 2, 3, 4]), 4)), [4, 3, 2, 1]);
});

test('a list shorter than k keeps its order', () => {
  assert.deepEqual(valuesOf(reverseKGroup(build([1, 2, 3]), 5)), [1, 2, 3]);
});

test('an empty list stays empty, and a single node stays', () => {
  assert.equal(reverseKGroup(null, 2), null);
  assert.deepEqual(valuesOf(reverseKGroup(build([7]), 2)), [7]);
});

test('six nodes in groups of 2: every group is complete', () => {
  assert.deepEqual(valuesOf(reverseKGroup(build([1, 2, 3, 4, 5, 6]), 2)), [2, 1, 4, 3, 6, 5]);
});

test('the result holds exactly the nodes that were given, each once, with their values unchanged', () => {
  const given = nodesOf(build(Array.from({ length: 10 }, (_, i) => i)));
  const values = new Map(given.map((n) => [n, n.val]));
  const result = nodesOf(reverseKGroup(given[0], 3));
  assert.equal(result.length, 10);
  assert.ok(result.every((n) => values.has(n) && values.get(n) === n.val));
  assert.equal(new Set(result).size, 10);
});

test('every k from 1 to 9 on random lists of 0 to 30 nodes', () => {
  for (let t = 0; t < 300; t++) {
    const values = Array.from({ length: randint(0, 30) }, () => randint(0, 99));
    for (let k = 1; k <= 9; k++) assert.deepEqual(valuesOf(reverseKGroup(build(values), k)), model(values, k), `k = ${k}`);
  }
});
A hint

Use a dummy node in front of the head and keep before, the node in front of the next group. Before reversing a group, walk k nodes from before.next to check that the group is complete. Reverse those k nodes with the three-pointer step, then reconnect: before.next becomes the group's old last node, and the group's old first node points at the rest of the list and becomes the new before.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 In the reversal loop, why must nxt = curr.next come before curr.next = prev?

    Choose one answer.

    Show the answer to question 1

    Answer: After the assignment, curr.next points backwards, and nothing else refers to the untouched rest of the list

    curr.next is the only reference to the nodes not yet visited. Once it is turned around to point at prev, the rest of the list is lost unless it was saved first, and the loop would stop after one node.

  2. Question 2 of 5 How many times does the loop body run when it reverses a list of 4 nodes?

    Type a number.

    Show the answer to question 2

    Answer: 4 times

    Each pass turns exactly one node around, so four nodes take four passes, as the trace shows: after step 4 the untouched part is empty and the loop ends with prev on the new head.

  3. Question 3 of 5 What does reverse_between.py print?

    What does this program print? Choose one answer.

    """Reverse only the nodes at positions left to right (counted from 1), with a dummy node in front of the head."""
    
    
    class Node:
        __slots__ = ("value", "next")
    
        def __init__(self, value, next=None):
            self.value = value
            self.next = next
    
    
    def build(values):
        head = None
        for v in reversed(values):
            head = Node(v, head)
        return head
    
    
    def values_of(node):
        out = []
        while node is not None:
            out.append(node.value)
            node = node.next
        return out
    
    
    def reverse_between(head, left, right):
        dummy = Node(None, head)
        before = dummy
        for _ in range(left - 1):  # 1. stop on the node before the sublist
            before = before.next
        first = before.next  # this node will end the reversed sublist
        prev, curr = None, first
        for _ in range(right - left + 1):  # 2. reverse exactly right - left + 1 nodes
            nxt = curr.next
            curr.next = prev
            prev, curr = curr, nxt
        before.next = prev  # 3. reconnect the front: before -> the old last node of the sublist
        first.next = curr  # 4. reconnect the back: the old first node -> the rest of the list
        return dummy.next
    
    
    for values, left, right in [([1, 2, 3, 4, 5, 6], 2, 5), ([1, 2, 3, 4, 5, 6], 1, 3), ([1, 2, 3, 4, 5, 6], 4, 6), ([7, 8], 1, 2), ([9], 1, 1)]:
        result = values_of(reverse_between(build(values), left, right))
        print(f"reverse positions {left}..{right} of {values}: {result}")
    Show the answer to question 3

    Answer: it prints

    reverse positions 2..5 of [1, 2, 3, 4, 5, 6]: [1, 5, 4, 3, 2, 6]
    reverse positions 1..3 of [1, 2, 3, 4, 5, 6]: [3, 2, 1, 4, 5, 6]
    reverse positions 4..6 of [1, 2, 3, 4, 5, 6]: [1, 2, 3, 6, 5, 4]
    reverse positions 1..2 of [7, 8]: [8, 7]
    reverse positions 1..1 of [9]: [9]

    The sublist is reversed in place and both of its ends are reconnected: the node in front of it points at the old last node of the sublist, and the old first node points at the rest of the list, so nothing is lost at either end. The dummy node makes positions that start at 1 work the same way.

  4. Question 4 of 5 A recursive reversal works on 500 nodes but raises RecursionError on 5,000 in CPython. Why?

    Choose one answer.

    Show the answer to question 4

    Answer: Every node adds a call that stays on the stack until the end of the list is reached, and CPython stops at its recursion limit, 1,000 by default

    The recursive version reverses the rest of the list before it can fix the current node, so all n calls wait at once. sys.getrecursionlimit() returns 1,000 by default; the loop version needs no extra frames at all.

  5. Question 5 of 5 1 → 2 → 3 → 4 → 5 is reversed in groups of 2, keeping a short final group as it is. What is the result?

    Choose one answer.

    Show the answer to question 5

    Answer: 2 → 1 → 4 → 3 → 5

    The groups are (1, 2), (3, 4) and the short group (5). The two full groups are reversed and reconnected, and 5 stays where it is because the count before each group finds fewer than 2 nodes left.

The reversal's invariant, proved, and the loop in four more languages Pro: opens with a Pro, Premium or Ultimate pass

The loop is short enough to get right by luck and to get wrong by one swapped line. This part states the invariant that makes it correct, proves that each pass keeps it and that the loop ends with the reversed list, and runs the same loop in JavaScript, Java, C++ and Go, where the language decides how the three assignments may be written.

In this part:

  • The invariant, and the proof that no node is lost
  • Three slips, and what each one does to 1 → 2 → 3
  • The same loop in JavaScript, Java, C++ and Go

3 runnable examples (C++, Go, Java, JavaScript, reverse_session.py, session.py) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Recursive reversal and the stack, measured Pro: opens with a Pro, Premium or Ultimate pass

The recursive reversal is a favourite in interviews because it is elegant and because its failure is easy to explain once measured. This part shows how it works, finds the longest list it can reverse under Python’s default limit, and compares four ways to reverse a list on time, memory and risk.

In this part:

  • How the recursive version turns the list around
  • Measured: where the recursive version stops
  • Four ways to reverse, compared

1 runnable example (Python) · about 2 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Sublists, groups of k and palindromes: dry runs, edge cases and a practice set Pro: opens with a Pro, Premium or Ultimate pass

Reversing part of a list is where off-by-one errors live: a group counted wrong, a reconnection forgotten, a short final group reversed by mistake. The groups of k are traced step by step, the palindrome check restores the list it inspects, and a list of boundary inputs and a few worked questions close the part.

In this part:

  • A dry run of groups of k, with the count that protects the short tail
  • Palindromes: reverse half, compare, and put it back
  • Edge cases to test before you submit
  • A practice set with answers

2 runnable examples (Python) · about 3 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Mid to senior

    A teammate defends a recursive list reversal because it is "tail recursive": each call ends with the recursive call. On a list of a million nodes, is it safe in Python, Java, C++ built with clang -O2, and Scheme? How would you make sure in each?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Fresher to mid

    Reverse the nodes between positions left and right of a singly linked list in one pass. Walk through 1 → 2 → 3 → 4 → 5 with left = 2 and right = 4, name every pointer you change, and say which inputs break code that has no dummy node.

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    How do you reverse a doubly linked list with head and tail sentinels in place, and which links do candidates usually forget? Then explain how a list class could offer reversal in O(1) time, and what that costs every other operation.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Reverse a list with three references, saving next before turning curr around; it is O(n) time and O(1) memory.
  • The recursive version keeps a waiting call per node and stops at Python’s recursion limit, 1,000 frames by default.
  • To reverse a sublist, stop on the node before it, reverse exactly its nodes, then reconnect both ends.
  • Groups of k are consecutive sublists; count each group before reversing it so that a short final group stays as it is.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.