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.

Coding Interview Patterns  Module 10 – Linked list patterns

Merging, splitting and partitioning lists

Partition a linked list around a value while keeping the order, merge two sorted lists, split a list into k parts, and sort it with merge sort.

  • Intermediate
  • 35 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 intro, Pro: 8 of 35 minutes free
Free: The problem: express orders first. With a pass: the other 12 sections and 3 interview answers, about 27 minutes.

What you will learn

  • Partition a list around a value in one pass with two dummy tails, keeping the order inside each group
  • Merge two sorted lists with a dummy head so that equal values keep their order
  • Split a list into k parts whose sizes differ by at most one
  • Sort a linked list with merge sort in O(n log n) time, and explain the first-middle split
  • Compare the recursive merge sort with a bottom-up one that needs no recursion

Before you start

What you’ll learn · Before you start

What you will learn

  • Partition a list around a value in one pass with two dummy tails, keeping the order inside each group
  • Merge two sorted lists with a dummy head so that equal values keep their order
  • Split a list into k parts whose sizes differ by at most one
  • Sort a linked list with merge sort in O(n log n) time, and explain the first-middle split
  • Compare the recursive merge sort with a bottom-up one that needs no recursion

Before you start

On this page

Merging, splitting and partitioning lists all build new chains out of nodes that already exist. Partitioning lists around a value moves every node below the value in front of the others and keeps both groups in their order. One pass does it: keep two chains, each behind a dummy node with a tail pointer, append every node to the chain it belongs to, and join the two chains at the end, with O(1) extra memory. Merging two sorted lists appends the smaller front node each time. Splitting a list into k parts cuts it after n // k nodes, one more for the first n mod k parts. Merge sort on a list combines the last two ideas and sorts in O(n log n) time.

The problem: express orders first

The problem

A grocery delivery service keeps the day’s orders as a singly linked list of delivery times, in minutes. Before dispatch, every order due in less than limit minutes must come before every other order, and inside each group the orders keep their order. Rearrange the nodes you are given, creating none except dummies, with O(1) extra memory, and return the new head. A list has up to 10⁵ orders.

With limit = 40, 45 → 20 → 90 → 15 → 60 → 30 becomes 20 → 15 → 30 → 45 → 90 → 60. An order due in exactly 40 minutes is not express, so 40 → 25 becomes 25 → 40, and a list with no express order, such as 50 → 70, stays as it is.

The brute force: swap neighbours until nothing moves

The idea of bubble sort respects every rule: whenever a standard order is directly followed by an express one, swap the two nodes, and repeat whole passes until none swaps. Swapping only neighbours that are out of place keeps each group’s order, and nothing is stored, but an express order at the end must step past every standard order before it.

Swap neighbouring nodes until no pass changes anything

brute/brute_force.py

"""The brute force for putting express orders first: swap neighbouring nodes, as bubble sort would.

Whenever a standard order is followed directly by an express order, swap the two nodes (not their values), and
repeat until a pass makes no swap. Swapping only neighbours that are out of place keeps each group's order, and it
needs O(1) memory, but an express order at the end must travel past every standard order before it.
"""
from lists import ListNode, from_list, show


def express_first_by_swaps(head, limit):
    """Return (new head, pairs of neighbours looked at)."""
    dummy = ListNode(None, head)
    looked, changed = 0, True
    while changed:
        changed = False
        prev = dummy
        while prev.next is not None and prev.next.next is not None:
            a, b = prev.next, prev.next.next
            looked += 1
            if a.val >= limit and b.val < limit:  # standard, then express: swap the two nodes
                a.next, b.next, prev.next = b.next, a, b
                changed = True
            prev = prev.next
    return dummy.next, looked


for times in ([45, 20, 90, 15, 60, 30], [50, 70], [40, 25]):
    head, looked = express_first_by_swaps(from_list(times), 40)
    print(f"{times} with limit 40: {show(head)}")

for n in (500, 1_000, 2_000):  # the worst case: every standard order first, then every express order
    kept, looked = express_first_by_swaps(from_list([90] * (n // 2) + [10] * (n // 2)), 40)
    print(f"n = {n:>5,}, express orders last: {looked:>9,} pairs looked at")
n = 100_000
work = (n // 2 + 1) * (n - 1)
print(f"n = {n:,}: about {work:,} pairs, {work / 1e7 / 60:.0f} minutes at 10^7 a second")

Output

[45, 20, 90, 15, 60, 30] with limit 40: 20 -> 15 -> 30 -> 45 -> 90 -> 60 -> None
[50, 70] with limit 40: 50 -> 70 -> None
[40, 25] with limit 40: 25 -> 40 -> None
n =   500, express orders last:   125,249 pairs looked at
n = 1,000, express orders last:   500,499 pairs looked at
n = 2,000, express orders last: 2,000,999 pairs looked at
n = 100,000: about 5,000,049,999 pairs, 8 minutes at 10^7 a second

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

brute/lists.py

"""Test helpers for singly linked lists: build one from a Python list, read it back, and print it."""


class ListNode:
    """One node: a value and a reference to the next node (None after the last node)."""

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


def from_list(values):
    """Build a linked list holding `values` in order and return its head (None for no values)."""
    dummy = ListNode(None)
    tail = dummy
    for v in values:
        tail.next = ListNode(v)
        tail = tail.next
    return dummy.next


def to_list(head):
    """The values from `head` to the end; raises ValueError if the list loops back on itself."""
    values, seen = [], set()
    node = head
    while node is not None:
        if id(node) in seen:
            raise ValueError(f"cycle: after {len(values)} nodes the list returns to a node already seen")
        seen.add(id(node))
        values.append(node.val)
        node = node.next
    return values


def show(head):
    """The list as one line of text, such as 4 -> 9 -> 15 -> None."""
    return " -> ".join([*map(str, to_list(head)), "None"])

When every express order comes last, each pass moves them one place, so n/2 + 1 passes look at n − 1 pairs each: 5 × 10⁹ pairs for 10⁵ orders, about 8 minutes of CPython. Copying the values into an array and writing them back is fast, but it costs O(n) memory and moves orders between nodes that other code may still hold.

What to notice

The answer is two lists glued together: the express orders in their original order, then the standard ones in theirs. Walking once and appending each node to the end of its own list keeps both orders automatically, and a dummy in front of each list removes the special case of the first append, as in the toolkit lesson.

The orders 45, 20, 90, 15, 60, 30 split into an express chain 20, 15, 30 and a standard chain 45, 90, 60, then joined.orders with limit 40, in one passinput452090156030express chain (val < 40)d201530tail estandard chaind459060Nonetail ss.next = Nonejoin: e.next =standard.nextresult: 20, 15, 30, 45, 90, 60

Two chains behind two dummy nodes, joined at the end

Text description of the diagram

The input list is 45, 20, 90, 15, 60, 30, with a limit of 40 minutes. The orders 20, 15 and 30 are below the limit and drawn in green; 45, 90 and 60 are not.

Below it are two chains, each starting at a dummy node drawn with a dashed border. The express chain holds 20, 15 and 30 in the order they appeared, and its tail pointer e is on 30. The standard chain holds 45, 90 and 60 in their order, and its tail pointer s is on 60, whose next is set to None.

An arrow from 30 to 45 shows the join, e.next = standard.next, which links the end of the express chain to the first real node of the standard chain. The result is 20, 15, 30, 45, 90, 60: every express order first, and each group in its original order.

Two dummy tails, step by step Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • The invariant, and the proof that the order is kept
    • A dry run on the example
  • Merging two sorted lists, with ties kept in order
  • Splitting into k parts: n // k nodes, one more for the first n mod k
  • Merge sort on a list in O(n log n)
  • The code in five languages
  • Edge cases and slips, caught by a stress test
  • Variants, with answers
  • Exercise: sort a list of orders by relinking its nodes
    • Sort a list of orders by relinking its nodes
  • A practice set with answers
  • Follow-up questions, answered
  • Check yourself
  • Interview questions
    • Why do two dummy nodes make a stable partition of a linked list simple?
    • Your stable partition of a linked list around a value passes the samples, but one hidden test times out and printing its result never ends. You build two chains behind dummies and join them. What is the most likely bug, which inputs trigger it, and how do you test for it?
    • Sort a singly linked list of up to 10⁶ nodes in O(n log n) time with O(1) extra memory: no recursion and no array. Describe the algorithm and its pointer bookkeeping, and say how you would test it.
    • A dispatcher splits a linked list of n orders into k vans, in order, so that loads differ by at most one and an earlier van is never lighter. n and k can reach 10⁶, and k can exceed n. Give the loads for n = 10 and k = 4, the algorithm, and what changes if the length is not known.
  • Key takeaways

5 runnable examples (C++, Go, Java, JavaScript, Python, lists.mjs, lists.py, measure.py) · 1 exercise with sample tests · 3 interview answers · about 23 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).

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.