← All Guides

Sorting

5 drills · 5 pattern exercises · 12 micro drills

Patterns Overview

Patterns used in this topic, with difficulty breakdown.

Shift & Place 1 drill 1 Easy
Divide & Merge 1 drill 1 Med
Partition 1 drill 1 Med
Two Pointers 1 drill 1 Easy
lo/mid/hi Pointers 1 drill 1 Med

Foundation Drill

29 Insertion sort Easy Shift & Place

Sort a list by repeatedly picking the next element and inserting it into its correct spot.

Start here in Foundations →

Coding Drills

All 5 drills grouped by pattern. Each includes prompt, solution, and complexity.

Shift & Place (1)

29 Insertion sort Easy

In plain English: Sort a list by repeatedly picking the next element and inserting it into its correct spot.

Builds the sorted portion one element at a time by shifting — efficient for nearly-sorted data because few shifts are needed.

Prompt

Sort an array using insertion sort.

Solution

def insertion_sort(a):
    for i in range(1, len(a)):
        key = a[i]
        j = i - 1
        while j >= 0 and a[j] > key:  # shift larger elements right
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = key
    return a

# Test: insertion_sort([5,3,1,4,2]) == [1,2,3,4,5]
O(n^2) time · O(1) space · Stable

Divide & Merge (1)

30 Merge sort Medium

In plain English: Sort a list by splitting it in half, sorting each half, and merging them back together.

Divide and conquer: splitting halves the problem, merging combines sorted halves in linear time — guaranteed O(n log n).

Prompt

Sort an array using merge sort.

Solution

def merge_sort(a):
    if len(a) <= 1:
        return a
    m = len(a) // 2  # divide at midpoint
    L = merge_sort(a[:m])
    R = merge_sort(a[m:])
    res, i, j = [], 0, 0
    while i < len(L) and j < len(R):
        if L[i] <= R[j]:
            res.append(L[i]); i += 1
        else:
            res.append(R[j]); j += 1
    res.extend(L[i:])
    res.extend(R[j:])
    return res

# Test: merge_sort([38,27,43,3,9,82,10]) == [3,9,10,27,38,43,82]
O(n log n) time · O(n) space · Stable

Partition (1)

31 Quick sort (Lomuto partition) Medium

In plain English: Sort a list by picking a pivot, putting smaller elements left and larger right, then recursing.

Partitioning places the pivot in its final sorted position — everything left is smaller, everything right is larger.

Prompt

Sort an array using quicksort with Lomuto partition.

Solution

def quicksort(a, lo=0, hi=None):
    if hi is None:
        hi = len(a) - 1
    if lo < hi:
        p = partition(a, lo, hi)
        quicksort(a, lo, p - 1)
        quicksort(a, p + 1, hi)
    return a

def partition(a, lo, hi):
    pivot = a[hi]
    i = lo  # i tracks boundary of <= pivot
    for j in range(lo, hi):
        if a[j] <= pivot:
            a[i], a[j] = a[j], a[i]
            i += 1
    a[i], a[hi] = a[hi], a[i]
    return i

# Test: quicksort([10,7,8,9,1,5]) == [1,5,7,8,9,10]
O(n log n) avg · O(n^2) worst · O(log n) space

Two Pointers (1)

32 Merge two sorted arrays Easy

In plain English: Combine two sorted arrays into one — the merge step that powers merge sort.

The merge step is the workhorse of merge sort — two pointers always pick the smaller remaining element.

Prompt

Merge two sorted arrays into one sorted array. (Speed drill — same as #6.)

Solution

def merge(a, b):
    res, i, j = [], 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            res.append(a[i]); i += 1
        else:
            res.append(b[j]); j += 1
    res.extend(a[i:])
    res.extend(b[j:])
    return res

# Test: merge([1,3,5,7], [2,4,6,8]) == [1,2,3,4,5,6,7,8]
O(n+m) time · O(n+m) space

lo/mid/hi Pointers (1)

33 Dutch national flag (3-way partition) Medium

In plain English: Sort an array containing only 0s, 1s, and 2s in a single pass.

Three pointers carve the array into four regions: 0s, 1s, unknown, and 2s — each swap shrinks the unknown region.

Prompt

Sort an array of 0s, 1s, and 2s in one pass.

Solution

def dutch_flag(a):
    lo, mid, hi = 0, 0, len(a) - 1
    # 0: swap to lo, 2: swap to hi, 1: skip
    while mid <= hi:
        if a[mid] == 0:
            a[lo], a[mid] = a[mid], a[lo]
            lo += 1; mid += 1
        elif a[mid] == 1:
            mid += 1
        else:
            a[mid], a[hi] = a[hi], a[mid]
            hi -= 1
    return a

# Test: dutch_flag([2,0,1,2,1,0]) == [0,0,1,1,2,2]
O(n) time · O(1) space

Pattern Recognition

5 exercises: spot the signals, pick the method, avoid the trap.

29 Insertion sort Easy

Sort an array using insertion sort.

Signals

Sorting

For each element, shift larger elements right and insert. O(n²) worst case, O(n) best (nearly sorted).

Trap

This is an algorithm implementation drill, not a problem-solving drill. Know the invariant: left portion is always sorted.

30 Merge sort Medium

Sort an array using merge sort.

Signals

Divide & Conquer

Split array in half, recursively sort, merge sorted halves. O(n log n) time, O(n) space.

Trap

The merge step is where the work happens — two pointers picking the smaller element from each sorted half.

31 Quick sort (Lomuto partition) Medium

Sort an array using quicksort with Lomuto partition.

Signals

Divide & Conquer

Partition places pivot correctly. Recurse on left and right partitions. O(n log n) average.

Trap

Worst case is O(n²) with bad pivot choice (already sorted). Random pivot selection avoids this.

32 Merge two sorted arrays Easy

Merge two sorted arrays into one sorted array.

Signals

Two Pointers

One pointer per array, always pick the smaller front element. O(n+m).

Trap

Concatenate + re-sort is O((n+m) log(n+m)). Merge is O(n+m) — exploit the sorted property.

33 Dutch national flag (3-way partition) Medium

Sort an array of 0s, 1s, and 2s in one pass.

Signals

Three Pointers

lo/mid/hi pointers. Swap 0s to lo, 2s to hi, skip 1s. One pass, O(n) time, O(1) space.

Sorting

Counting sort (count 0s, 1s, 2s, overwrite) also O(n) but requires two passes.

Trap

General sorting is O(n log n). This is a special case with only 3 values → can do O(n).

Related Micro Drills

12 quick recall drills to reinforce this topic.

87 Move zeros to end Pointer Manipulation 10s

Move-zeroes is remove-element followed by a fill. Classic two-pointer warm-up.

w = 0
for r in range(len(a)):
    if a[r] != 0:
        a[w] = a[r]
        w += 1
while w < len(a):
    a[w] = 0
    w += 1
126 Remove duplicates in-place Pointer Manipulation 10s

Write-pointer compaction for sorted arrays. Returns new length.

w = 1
for i in range(1, len(a)):
    if a[i] != a[i-1]:
        a[w] = a[i]
        w += 1
return w
73 Sort with key function Divide & Conquer 5s

Custom key sorting is Python's primary sort mechanism. Foundation for interval and custom-order problems.

a.sort(key=lambda x: x[1])
76 Merge two sorted lists Divide & Conquer 15s

Merging sorted lists is the core of merge sort and merge-k-lists.

i = j = 0
merged = []
while i < len(a) and j < len(b):
    if a[i] <= b[j]:
        merged.append(a[i]); i += 1
    else:
        merged.append(b[j]); j += 1
merged.extend(a[i:])
merged.extend(b[j:])
113 Merge intervals Interval & Math 15s

Sort-then-merge is the standard O(n log n) approach to interval union.

a.sort()
res = [a[0]]
for s, e in a[1:]:
    if s <= res[-1][1]:
        res[-1][1] = max(res[-1][1], e)
    else:
        res.append([s, e])
117 Spiral order traversal Interval & Math 15s

Spiral traversal uses shrinking boundaries: top, bottom, left, right.

res = []
t, b, l, r = 0, len(m)-1, 0, len(m[0])-1
while t <= b and l <= r:
    for c in range(l, r+1): res.append(m[t][c])
    t += 1
    for c in range(t, b+1): res.append(m[c][r])
    r -= 1
9 Partition around pivot (Lomuto) Pointer Manipulation 15s

Lomuto partition is the core of quicksort and quickselect. Know it cold for kth-element problems.

def partition(a, lo, hi):
    pivot = a[hi]
    i = lo
    for j in range(lo, hi):
        if a[j] <= pivot:
            a[i], a[j] = a[j], a[i]
            i += 1
    a[i], a[hi] = a[hi], a[i]
    return i
77 Partition step (quicksort) Divide & Conquer 15s

Partition is the heart of quicksort. Same as Lomuto partition — drill until automatic.

def partition(a, lo, hi):
    pivot = a[hi]
    i = lo
    for j in range(lo, hi):
        if a[j] <= pivot:
            a[i], a[j] = a[j], a[i]
            i += 1
    a[i], a[hi] = a[hi], a[i]
    return i
84 Converging two pointers Pointer Manipulation 10s

Converging pointers shrink the search space from both ends. Core of two-sum-sorted and palindrome.

l, r = 0, len(a) - 1
while l < r:
    # process a[l], a[r]
    l += 1; r -= 1
10 Dutch flag (3-way partition) Pointer Manipulation 15s

Three-way partition handles duplicates efficiently. Classic for sort-colors type problems.

lo, mid, hi = 0, 0, len(a) - 1
while mid <= hi:
    if a[mid] < pivot:
        a[lo], a[mid] = a[mid], a[lo]
        lo += 1; mid += 1
    elif a[mid] == pivot:
        mid += 1
    else:
        a[mid], a[hi] = a[hi], a[mid]
        hi -= 1
274 Find peak element (binary search) Search 10s

Move toward the higher neighbor — a peak must exist on that side. Uses lo < hi (not <=) and hi = mid (not mid-1).

lo, hi = 0, len(a) - 1
while lo &lt; hi:
    mid = (lo + hi) // 2
    if a[mid] &lt; a[mid + 1]:
        lo = mid + 1
    else:
        hi = mid
return lo
46 Binary search template Search 15s

The standard binary search template. Foundation for all logarithmic search problems.

lo, hi = 0, len(a) - 1
while lo &lt;= hi:
    mid = lo + (hi - lo) // 2
    if a[mid] == target: return mid
    elif a[mid] &lt; target: lo = mid + 1
    else: hi = mid - 1