0/23in Data Structures & Algorithms
Data Structures & Algorithms

Sorting Foundations

How comparison sorts actually move data. Watch insertion and merge sort rearrange an array in real time.

~15 minLesson 34 of 60
Your progressNot started

You will rarely implement a sort in production — your language ships a superb one — but sorting is where you build intuition for how algorithms move data, and it comes up constantly as a subroutine. Understanding the mechanics also explains why the standard library sort has the complexity it does.

Insertion sort: how humans sort cards

Insertion sort keeps a growing sorted prefix on the left. Each step takes the next unsorted element and slides it left until it lands in the right place — exactly how most people sort a hand of playing cards.

Insertion sortStep 1 / 29
5
2
8
1
9
3
7
4
Unsorted input. Insertion sort grows a sorted prefix one element at a time.
comparing / shiftingsorted prefix

Watch the green prefix grow one element at a time. The orange highlights show the comparisons and shifts needed to open a gap for the incoming value. When the input is already nearly sorted, each element barely moves — insertion sort is O(n) in the best case, which is why real-world sorts use it for small or almost-sorted ranges.

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

Its worst case is O(n²) — a reverse-sorted array makes every element travel the full width. That’s fine for tiny arrays and disastrous for large ones.

The complexity landscape

Algorithm Best Average Worst Space Stable?
Insertion O(n) O(n²) O(n²) O(1) Yes
Merge O(n log n) O(n log n) O(n log n) O(n) Yes
Quick O(n log n) O(n log n) O(n²) O(log n) No
Heap O(n log n) O(n log n) O(n log n) O(1) No

O(n log n) is the floor for any comparison-based sort — you cannot do better in general, because distinguishing all n! orderings requires at least that many comparisons.

What your standard library actually does

Production sorts are hybrids. Python’s Timsort and many others detect already-sorted “runs” and use insertion sort for small chunks, then merge them — getting O(n) on friendly inputs and O(n log n) guaranteed on hostile ones. Knowing this lets you answer “what sort does your language use?” with more than a shrug.

Stability matters more than you’d think

A stable sort preserves the relative order of equal elements. That’s what makes multi-key sorting work: sort by name, then by department, and people stay alphabetized within each department. Reach for a stable sort whenever you sort by one key after another.

Practical rule: never hand-roll a sort in an interview unless asked. Call the library sort, state that it’s O(n log n), and spend your energy on the part of the problem that’s actually interesting.

Pattern recognition

Variations

Worked problems

These are sorting patterns, not sorting implementations. The interview signal is choosing the invariant that sorted order gives you.

LeetCode 75Medium
  • Sorting
  • Two pointers
  • Partition

Sort Colors

Given an array containing only 0, 1, and 2, reorder it in-place so equal values are grouped in ascending order.

Approach. Use the Dutch national flag invariant. Everything before lo is 0; everything after hi is 2; i scans the unknown middle. Swap 0 left, swap 2 right, and leave 1 in the middle.

Show solution
def sort_colors(nums):
    lo, i, hi = 0, 0, len(nums) - 1

    while i <= hi:
        if nums[i] == 0:
            nums[lo], nums[i] = nums[i], nums[lo]
            lo += 1
            i += 1
        elif nums[i] == 2:
            nums[i], nums[hi] = nums[hi], nums[i]
            hi -= 1
        else:
            i += 1

Complexity. O(n) time and O(1) space. The array is partitioned in one pass; no comparison sort is needed for three known values.

LeetCode 56Medium
  • Sorting
  • Intervals
  • Sweep

Merge Intervals

Given a list of intervals, merge all overlapping intervals and return the non-overlapping result.

Approach. Sort by start time. Then the only interval that can overlap the current one is the last interval in the merged output. If starts overlap, extend the end; otherwise start a new merged interval.

Show solution
def merge(intervals):
    intervals.sort(key=lambda x: x[0])
    merged = []

    for start, end in intervals:
        if not merged or start > merged[-1][1]:
            merged.append([start, end])
        else:
            merged[-1][1] = max(merged[-1][1], end)

    return merged

Complexity. O(n log n) time from sorting and O(n) space for the output. The sweep after sorting is linear.

LeetCode 179Medium
  • Sorting
  • Custom comparator

Largest Number

Given nonnegative integers, arrange them so their concatenation forms the largest possible number. Return it as a string.

Approach. The order between two numbers a and b should put a first if a + b is larger than b + a. That local comparator produces the global order. After sorting, handle the all-zero case so the answer is "0", not repeated zeros.

Show solution
from functools import cmp_to_key


def largest_number(nums):
    parts = [str(x) for x in nums]

    def compare(a, b):
        if a + b > b + a:
            return -1
        if a + b < b + a:
            return 1
        return 0

    parts.sort(key=cmp_to_key(compare))
    if parts[0] == "0":
        return "0"
    return "".join(parts)

Complexity. O(n log n · k) time, where k is the maximum digit length, because comparisons concatenate short strings. Space is O(n · k) for the string parts and sorted output.

Your progressNot started