← All Posts
DSA Series · Arrays · Sorting Techniques · Overview

Sorting Techniques: The Complete Guide

Sorting is the single most studied problem in computer science. Nearly every non-trivial program sorts data at some point. Search engines rank results. Databases index rows. Graphics pipelines depth-sort polygons. Compilers order instructions. Understanding sorting is not optional.

This series covers every sorting algorithm you will encounter in practice, from the elementary quadratic sorts to the efficient $O(n \log n)$ divide-and-conquer algorithms, to the linear-time non-comparison sorts. Each post includes interactive step-through animations, C++ implementations, and detailed complexity analysis.

Why Sorting Matters

Sorting transforms an unstructured collection into an ordered one. Once data is sorted, many problems become trivially easy:

In competitive programming, sorting is often the first step in a solution. If you see a problem that asks for "the minimum number of..." or "the maximum...", there is a good chance sorting is involved.

Key insight: Sorting often converts an $O(n^2)$ brute-force solution into an $O(n \log n)$ one. When in doubt, sort first.

Comparison-Based vs Non-Comparison Sorts

There are two fundamentally different categories of sorting algorithms:

Comparison-based sorts

These algorithms determine order by comparing pairs of elements using a less-than or equal-to operator. The only information they use about the data is the result of comparisons. Examples: merge sort, quick sort, heap sort, insertion sort, bubble sort.

There is a proven lower bound for comparison-based sorting:

$$\Omega(n \log n)$$

No comparison-based sort can do better than $n \log n$ comparisons in the worst case. The proof uses a decision tree argument: with $n!$ possible permutations, any comparison tree needs at least $\log_2(n!) = \Omega(n \log n)$ leaves.

Non-comparison sorts

These algorithms exploit additional structure in the data (integer keys, bounded range, digit decomposition) to sort without comparing elements. They can break the $O(n \log n)$ barrier. Examples: counting sort, radix sort, bucket sort.

The tradeoff: non-comparison sorts are faster when applicable, but they have constraints on input type (usually integers in a known range) and often use more memory.

Sorting Algorithms Comparison-Based Non-Comparison Bubble Insertion Merge Sort Quick Sort Selection Heap Sort Counting Radix Bucket Lower bound: O(n log n) Can achieve O(n)
Sorting algorithm taxonomy. Green-bordered algorithms are covered in dedicated posts in this series.

Stability

A sorting algorithm is stable if it preserves the relative order of elements with equal keys. This matters when sorting by multiple criteria.

Example: you sort a list of students by name, then by grade. With a stable sort, students with the same grade remain in alphabetical order. With an unstable sort, their name order may be scrambled.

▶ Stability Demonstration

Cards with the same number but different suits. Watch how a stable sort preserves suit order while an unstable sort may not.

In-Place vs Out-of-Place

An in-place sorting algorithm uses $O(1)$ extra memory (or at most $O(\log n)$ for the recursion stack). It rearranges elements within the original array. Quick sort and heap sort are in-place. Merge sort is not; it needs $O(n)$ auxiliary space for merging.

In practice, the memory overhead matters. If your data barely fits in RAM, an in-place sort saves you from thrashing. If memory is plentiful, the extra allocation of merge sort is negligible.

Complexity Comparison

The table below summarizes time and space complexity for all major sorting algorithms. This is the reference you will come back to repeatedly.

Algorithm Best Average Worst Space Stable Type
Bubble Sort $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$ Stable Comparison
Selection Sort $O(n^2)$ $O(n^2)$ $O(n^2)$ $O(1)$ Unstable Comparison
Insertion Sort $O(n)$ $O(n^2)$ $O(n^2)$ $O(1)$ Stable Comparison
Merge Sort $O(n \log n)$ $O(n \log n)$ $O(n \log n)$ $O(n)$ Stable Comparison
Quick Sort $O(n \log n)$ $O(n \log n)$ $O(n^2)$ $O(\log n)$ Unstable Comparison
Heap Sort $O(n \log n)$ $O(n \log n)$ $O(n \log n)$ $O(1)$ Unstable Comparison
Counting Sort $O(n + k)$ $O(n + k)$ $O(n + k)$ $O(n + k)$ Stable Non-Comparison
Radix Sort $O(d \cdot (n + k))$ $O(d \cdot (n + k))$ $O(d \cdot (n + k))$ $O(n + k)$ Stable Non-Comparison
Bucket Sort $O(n + k)$ $O(n + k)$ $O(n^2)$ $O(n + k)$ Stable Non-Comparison

Where $k$ is the range of input values and $d$ is the number of digits.

Visual Comparison Chart

The chart below gives an intuitive sense of how growth rates differ. For $n = 100$ elements:

Operations (log scale) Input size n n n log n n² n³ O(n) O(n log n) O(n²) 10 50 100 500 1k 5k
Growth rate comparison. At $n = 10{,}000$, an $O(n^2)$ sort does $10^8$ operations while $O(n \log n)$ does roughly $130{,}000$.

Choosing the Right Sort

There is no single "best" sorting algorithm. The right choice depends on your constraints:

The $\Omega(n \log n)$ Lower Bound

Why can we not sort faster than $n \log n$ with comparisons? The proof is elegant.

Any comparison-based sort can be modeled as a decision tree: a binary tree where each internal node is a comparison ("is $a[i] \leq a[j]$?"), each leaf is a permutation of the input, and the path from root to leaf is the sequence of comparisons made.

For $n$ elements, there are $n!$ possible input permutations. The decision tree must have at least $n!$ leaves (one for each permutation). A binary tree with $L$ leaves has height at least $\log_2 L$. Therefore:

$$\text{height} \geq \log_2(n!) = \sum_{i=1}^{n} \log_2 i \geq \int_1^n \log_2 x \, dx = \Theta(n \log n)$$

This means any comparison-based sort must make at least $\Omega(n \log n)$ comparisons in the worst case. Merge sort and heap sort are asymptotically optimal because they match this bound.

What This Series Covers

Part 1: Overview (this page)

Why sorting matters, taxonomy, stability, complexity table, the lower bound proof.

Part 2: Comparison Sorts

Merge sort and quick sort in depth. Divide-and-conquer animations, Lomuto and Hoare partitions. C++ code.

Part 3: Linear Time Sorts

Counting sort, radix sort, bucket sort. Step-through counting animation. When linear-time sorting applies.

Part 4: Competition Patterns

Custom comparators, sort + two pointers, sort + binary search, interval scheduling, inversion count.

A Note on std::sort

In C++, std::sort uses introsort: a hybrid of quick sort, heap sort, and insertion sort. It starts with quick sort, switches to heap sort if recursion depth exceeds $2 \log n$ (to guarantee $O(n \log n)$ worst case), and uses insertion sort for small sub-arrays (typically $n \leq 16$).

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> arr = {38, 27, 43, 3, 9, 82, 10};
    std::sort(arr.begin(), arr.end());          // ascending
    std::sort(arr.begin(), arr.end(), std::greater<int>()); // descending
    // For stable sort:
    std::stable_sort(arr.begin(), arr.end());   // guaranteed stable
}

std::stable_sort uses merge sort internally and guarantees $O(n \log n)$ with stability, at the cost of $O(n)$ extra memory.

Summary