Skip to content
← All posts

Sort List: Merge Sort on a Linked List

5 min read
leetcodeproblemmediumlinked-listssorting

Sort List is LeetCode problem 148. It asks you to sort a linked list in ascending order, ideally in O(n log n) time. The natural choice is merge sort, and it maps beautifully onto linked lists because splitting and merging are both pointer operations with no random access needed.

If you already know how to find the middle of a linked list and how to merge two sorted lists, this problem is just combining those two building blocks inside a recursive framework.

The problem

Given the head of a linked list, return the list after sorting it in ascending order.

Example: [4, 2, 1, 3] becomes [1, 2, 3, 4]. Another example: [-1, 5, 3, 4, 0] becomes [-1, 0, 3, 4, 5].

Follow up: Can you sort the linked list in O(n log n) time and O(1) memory (i.e. constant space)?

split421342134213merge24131234
Merge sort on a linked list: recursively split into halves, then merge sorted halves back together.

The approach: merge sort

Merge sort works by splitting the input in half, recursively sorting each half, and merging the two sorted halves. On arrays you need O(n) extra space for the merge buffer. On linked lists, the merge is done in-place by rearranging pointers, so no extra buffer is needed.

The algorithm has three parts:

  1. Find the middle using the slow/fast pointer technique. slow moves one step at a time, fast moves two. When fast reaches the end, slow is at the midpoint.
  2. Split the list into two halves by cutting the link after slow.
  3. Recursively sort each half, then merge the two sorted halves using the standard two-pointer merge.

The base case is a list with zero or one node, which is already sorted.

Visual walkthrough

Let's trace through [4, 2, 1, 3] step by step. First you find the middle and split, then recurse down to single nodes, then merge back up.

Step 1: Find the middle. slow and fast both start at head.

4213slowfast

Use the slow/fast pointer technique. slow moves 1 step, fast moves 2 steps.

Step 2: Advance pointers. slow = node 2, fast = node 1.

4213slowfast

After one iteration: slow is at index 1, fast is at index 2.

Step 3: fast.next.next is null. Stop. slow is the midpoint.

4213slow

Cut the link after slow. Left half: [4, 2]. Right half: [1, 3].

Step 4: Recurse on left half [4, 2]. Split into [4] and [2].

42

Single-node lists are already sorted. Now merge them.

Step 5: Merge [4] and [2]. Compare heads, pick smaller first.

42p1p2result24

2 is smaller than 4. Result: [2, 4]. Left half is now sorted.

Step 6: Recurse on right half [1, 3]. Split into [1] and [3].

13

Single-node lists again. Merge them.

Step 7: Merge [1] and [3]. 1 is smaller.

13p1p2result13

1 is smaller than 3. Result: [1, 3]. Right half is now sorted.

Step 8: Merge [2, 4] and [1, 3]. Compare 2 vs 1. Pick 1.

24p113p2

1 from right half is smaller. Result so far: [1].

Step 9: Compare 2 vs 3. Pick 2. Then compare 4 vs 3. Pick 3. Then append 4.

1234

Final sorted list: [1, 2, 3, 4]. Done!

Notice how the recursion bottoms out at single-node lists (which are trivially sorted), and then the merge steps build up the sorted result from the bottom. Each merge uses the same two-pointer technique as Merge Two Sorted Lists.

The code

Here is the Python solution:

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

def sort_list(head):
    if not head or not head.next:
        return head

    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

    mid = slow.next
    slow.next = None

    left = sort_list(head)
    right = sort_list(mid)

    return merge(left, right)

def merge(l1, l2):
    dummy = ListNode(0)
    curr = dummy

    while l1 and l2:
        if l1.val <= l2.val:
            curr.next = l1
            l1 = l1.next
        else:
            curr.next = l2
            l2 = l2.next
        curr = curr.next

    curr.next = l1 if l1 else l2
    return dummy.next

Key details to notice:

  • fast = head.next instead of fast = head. This ensures that for a two-node list, slow stays at the first node so you split into [first] and [second] rather than getting stuck in infinite recursion.
  • slow.next = None severs the link between the two halves. Without this, the left half would still point into the right half.
  • The merge function is identical to the solution for Merge Two Sorted Lists. It uses a dummy head node and two pointers, picking the smaller value at each step.
  • l1.val <= l2.val uses <= to maintain stability (equal values from the left half come first).

The split point matters. Setting fast = head.next makes slow land on the last node of the left half for even-length lists. This avoids the edge case where a two-node list would never split.

Complexity analysis

MetricValueReasoning
TimeO(n log n)Each level of recursion does O(n) work for splitting and merging. There are O(log n) levels since you halve the list each time.
SpaceO(log n)The recursion stack goes O(log n) levels deep. No extra data structures are used.

The follow-up asks for O(1) space. The recursive version uses O(log n) space due to the call stack. A bottom-up iterative merge sort can achieve true O(1) space by merging sublists of increasing size (1, 2, 4, 8, ...) without recursion. The recursive version shown here is the one you will typically need in interviews.

The building blocks

This problem composes two reusable building blocks that appear in many linked list problems.

Slow/fast pointer to find the middle

You set up two pointers at the head. slow advances one node per step, fast advances two. When fast reaches the end (or its next is null), slow is at the middle. This is the same technique used in:

Merge two sorted lists

Given two sorted linked lists, merge them into one by comparing heads and picking the smaller value. This is the complete solution to Merge Two Sorted Lists (LeetCode 21), and it also forms the core of:

  • Merge K Sorted Lists (LeetCode 23) using divide-and-conquer or a heap
  • Any divide-and-conquer algorithm on linked lists

Once these two building blocks are automatic, the sort list solution is just the framework that connects them.

Edge cases to watch for

  • Empty list. head is None. The base case returns None immediately.
  • Single node. head.next is None. Already sorted. The base case handles this.
  • Two nodes. [2, 1]. slow stays at node 0 (since fast = head.next and fast.next is None). Split into [2] and [1]. Merge produces [1, 2].
  • Already sorted. [1, 2, 3, 4]. The algorithm still splits and merges, but each merge step just walks through in order. No extra cost beyond the O(n log n) comparisons.
  • All identical values. [3, 3, 3]. The <= in the merge function handles this cleanly. The list comes back unchanged.

From understanding to recall

Merge sort on a linked list is a pattern that combines two well-known building blocks. Once you see it, the logic feels natural. But reproducing it under pressure requires remembering specific details: where to initialize fast, where to cut the list, and the dummy-head merge pattern.

Reading the solution once is not enough. The gap between "I understand this" and "I can write this from scratch" is where practice matters. Spaced repetition drills lock in those details so that when you see a sorting or linked list problem, the pieces snap together automatically.

Related posts


Visual Learner? Explore how linked list operations work with our Linked List Interactive Visualization.