Basic Sorts
Imagine you've just been dealt a hand of playing cards and you want to sort them. There are three natural, human-style approaches:
Option A: Scan left to right, swap any two neighbors that are out of order. Repeat until the hand is sorted. That's bubble sort.
Option B: Find the lowest card in your unsorted cards, pull it out, set it aside in sorted order. Repeat. That's selection sort.
Option C: Pick up cards one at a time and slide each new card into its correct position among the cards you're already holding. That's insertion sort.
All three are \(O(n^2)\) in the worst case. But they have very different personalities.
Bubble Sort — Neighbors Swap Their Way Up
Bubble sort does passes through the array, swapping adjacent pairs that are out of order. After each pass, the largest unsorted element has "bubbled" to its correct spot at the end. With an early-exit optimization (stop if a pass makes no swaps), it runs in \(O(n)\) on already-sorted input. In the worst case (reverse-sorted), it makes \(O(n^2)\) swaps. It's stable — equal elements never swap past each other.
Selection Sort — Always Find the Minimum
For each position i, selection sort scans the remaining unsorted portion to find the minimum element, then swaps it into position i. It always makes exactly n(n−1)/2 comparisons regardless of input — no early exit possible. It performs at most n−1 swaps, which is useful when writes are expensive. It's not stable: swapping the minimum into place can skip over equal elements, changing their relative order.
Insertion Sort — Slide Into Place
Insertion sort builds a sorted subarray from left to right. For each new element, it shifts elements in the sorted portion rightward until it finds the right slot to insert. On nearly-sorted input, very little shifting is needed: \(O(n)\) total. On reverse-sorted input, every element shifts past all previous ones: \(O(n^2)\). It's stable and in-place.
Insertion sort is the algorithm of choice for small arrays (n ≤ 16–32). Production sort implementations like Python's Timsort and C++'s Introsort fall back to insertion sort for small sub-arrays because its tiny constant and cache-friendly access pattern make it fastest there. For larger arrays, algorithms like merge sort and quicksort take over with their \(O(n \log n)\) performance.