05. Selection Sort & Insertion Sort
1817 words
9 min read
Visual companion
Python
Type and operator map
Python Week 1: the first filter for runtime behavior
View
Revision summary
What this note is really saying
Short form
# 05. Selection Sort & Insertion Sort > **What problem does this solve?** You have an unordered list.

05. Selection Sort & Insertion Sort
What problem does this solve? You have an unordered list. You need elements in ascending order. These are the two most intuitive sorting algorithms — but both are (O(n^2)) and only practical for small lists (<10,000 elements).
1. Selection Sort
Mental Model
You're sorting exam papers by score. You scan the entire pile, find the highest score, move it to a new pile. Repeat with the remaining papers. After (n) scans, the new pile is sorted.
How It Works
(Diagram)
Step-by-Step Trace
Sorting [64, 25, 12, 22, 11]:
pseudoInitial: [64, 25, 12, 22, 11] Pass 1: Find min in [64, 25, 12, 22, 11] → 11 at index 4 Swap 64 and 11 → [11, 25, 12, 22, 64] Pass 2: Find min in [25, 12, 22, 64] → 12 at index 2 Swap 25 and 12 → [11, 12, 25, 22, 64] Pass 3: Find min in [25, 22, 64] → 22 at index 3 Swap 25 and 22 → [11, 12, 22, 25, 64] Pass 4: Find min in [25, 64] → 25 at index 3 Already in place → [11, 12, 22, 25, 64] Sorted! [11, 12, 22, 25, 64]
Implementation
python# runnable def selection_sort(arr): """Sort arr in-place. Returns sorted array. Invariant: After i passes, first i elements are sorted and are the i smallest elements in the entire list. Time: O(n²) always Space: O(1) """ n = len(arr) for i in range(n - 1): # Find the minimum element in the unsorted portion min_idx = i for j in range(i + 1, n): if arr[j] < arr[min_idx]: min_idx = j # Swap the found minimum with the first unsorted position if min_idx != i: arr[i], arr[min_idx] = arr[min_idx], arr[i] return arr # Test arr = [64, 25, 12, 22, 11] result = selection_sort(arr.copy()) print(f"Sorted: {result}") # [11, 12, 22, 25, 64] # Swaps vs Comparisons analysis def selection_sort_analysis(arr): """Return sorted array plus comparison and swap counts.""" n = len(arr) comparisons = 0 swaps = 0 for i in range(n - 1): min_idx = i for j in range(i + 1, n): comparisons += 1 if arr[j] < arr[min_idx]: min_idx = j if min_idx != i: arr[i], arr[min_idx] = arr[min_idx], arr[i] swaps += 1 return arr, comparisons, swaps arr = [64, 25, 12, 22, 11] sorted_arr, comps, swps = selection_sort_analysis(arr) print(f"Sorted: {sorted_arr}, Comparisons: {comps}, Swaps: {swps}") # Sorted: [11, 12, 22, 25, 64], Comparisons: 10, Swaps: 4
Complexity Analysis
| Measurement | Value |
|---|---|
| Comparisons | (n(n-1)/2 = O(n^2)) always |
| Swaps | (n-1 = O(n)) |
| Best case | (O(n^2)) — even if already sorted, still scans everything |
| Worst case | (O(n^2)) |
| Average case | (O(n^2)) |
| Space | (O(1)) in-place |
| Stable? | No — the swap can move equal elements past each other |
2. Insertion Sort
Mental Model
You're sorting a hand of playing cards. You pick up cards one by one and insert each into its correct position among the already-sorted cards in your hand.
How It Works
(Diagram)
Step-by-Step Trace
Sorting [64, 25, 12, 22, 11]:
pseudoInitial: [64, 25, 12, 22, 11] Pass 1: key = 25, compare with 64, 64 > 25 → shift 64 right [64, 64, 12, 22, 11] → insert 25 → [25, 64, 12, 22, 11] Pass 2: key = 12, compare with 64, 64 > 12 → shift [25, 64, 64, 22, 11] → compare with 25, 25 > 12 → shift [25, 25, 64, 22, 11] → insert 12 → [12, 25, 64, 22, 11] Pass 3: key = 22, compare with 64 → shift [12, 25, 64, 64, 11] → compare with 25 → shift [12, 25, 25, 64, 11] → compare with 12 → stop, insert [12, 22, 25, 64, 11] Pass 4: key = 11, shift all 4 elements right, insert at front [11, 12, 22, 25, 64]
Implementation
python# runnable def insertion_sort(arr): """Sort arr in-place. Returns sorted array. Invariant: After i passes, first i elements are sorted (but not necessarily the smallest i elements). Time: O(n²) worst, O(n) best Space: O(1) """ n = len(arr) for i in range(1, n): key = arr[i] # Element to be inserted j = i - 1 # Shift elements greater than key to the right while j >= 0 and arr[j] > key: arr[j + 1] = arr[j] j -= 1 arr[j + 1] = key # Insert key in correct position return arr # Test arr = [64, 25, 12, 22, 11] result = insertion_sort(arr.copy()) print(f"Sorted: {result}") # [11, 12, 22, 25, 64] # Analysis with counters def insertion_sort_analysis(arr): """Return sorted array plus comparison and shift counts.""" n = len(arr) comparisons = 0 shifts = 0 for i in range(1, n): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: comparisons += 1 arr[j + 1] = arr[j] shifts += 1 j -= 1 if j >= 0: comparisons += 1 # The comparison that failed arr[j + 1] = key return arr, comparisons, shifts # Test on different inputs arr1 = [1, 2, 3, 4, 5] # Already sorted arr2 = [5, 4, 3, 2, 1] # Reverse sorted arr3 = [64, 25, 12, 22, 11] # Random for label, arr in [("Already sorted", arr1), ("Reverse sorted", arr2), ("Random", arr3)]: _, comps, shifts = insertion_sort_analysis(arr.copy()) print(f"{label}: {comps} comparisons, {shifts} shifts") # Already sorted: 4 comparisons, 0 shifts # Reverse sorted: 14 comparisons, 10 shifts # Random: 11 comparisons, 7 shifts
Recursive Insertion Sort
python# runnable def insertion_sort_recursive(arr, n=None): """Recursive insertion sort.""" if n is None: n = len(arr) if n <= 1: return arr # Sort first n-1 elements insertion_sort_recursive(arr, n - 1) # Insert last element in its correct position last = arr[n - 1] j = n - 2 while j >= 0 and arr[j] > last: arr[j + 1] = arr[j] j -= 1 arr[j + 1] = last return arr arr = [64, 25, 12, 22, 11] print(f"Recursive sort: {insertion_sort_recursive(arr)}")
Complexity Analysis
| Measurement | Value |
|---|---|
| Best case (already sorted) | (O(n)) — only 1 comparison per element |
| Worst case (reverse sorted) | (O(n^2)) — each element shifts all previous ones |
| Average case | (O(n^2)) — about half the elements shift |
| Comparisons | Best: (n-1), Worst: (n(n-1)/2) |
| Shifts | Same as comparisons in worst case |
| Space | (O(1)) in-place |
| Stable? | Yes — equal elements keep original order |
3. Comparison: Selection Sort vs Insertion Sort
| Feature | Selection Sort | Insertion Sort |
|---|---|---|
| Best case | (O(n^2)) | (O(n)) |
| Worst case | (O(n^2)) | (O(n^2)) |
| Average case | (O(n^2)) | (O(n^2)) |
| Swaps/Shifts | (n-1) swaps (few) | (O(n^2)) shifts (many) |
| Stable | No | Yes |
| Online (sort as data arrives) | No | Yes |
| Adaptive (fast on nearly sorted) | No | Yes |
| Comparisons | Always (n(n-1)/2) | Varies: (n-1) to (n(n-1)/2) |
When to use selection sort: When swapping is expensive (e.g., swapping large structures) — it never makes more than (n-1) swaps.
When to use insertion sort: When data is nearly sorted or arrives online (one element at a time). It's also used as the base case in some hybrid sorts (like Timsort).
4. Common Bugs
python# BUG 1: Off-by-one in selection sort range def buggy_sel_sort(arr): n = len(arr) for i in range(n): # Should be range(n-1) min_idx = i for j in range(i + 1, n): if arr[j] < arr[min_idx]: min_idx = j arr[i], arr[min_idx] = arr[min_idx], arr[i] return arr # Works but makes an unnecessary extra pass (swapping with itself) # BUG 2: Not using <= for stability in insertion sort def unstable_insertion(arr): for i in range(1, len(arr)): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: # Using > makes it stable arr[j + 1] = arr[j] j -= 1 arr[j + 1] = key # Using >= would make it unstable (equal elements reversed) # BUG 3: Forgetting to decrement j in insertion sort def infinite_insertion(arr): for i in range(1, len(arr)): key = arr[i] j = i - 1 while j >= 0 and arr[j] > key: arr[j + 1] = arr[j] # Missing j -= 1 → infinite loop!
Practice Questions
Q1. Trace selection sort on [3, 1, 4, 1, 5, 9, 2, 6]. Show the array after each pass.
Q2. How many comparisons does selection sort make on an array of 100 elements? How many swaps?
Q3. What input causes insertion sort to run in O(n) time? What input causes O(n²)?
Q4. Prove that after i iterations of selection sort's outer loop, the first i elements are the i smallest elements in the entire array.
Q5. After 3 iterations of insertion sort on [7, 2, 1, 9, 5, 3], what does the array look like?
Q6. Why is insertion sort preferred over selection sort for nearly sorted data?
Q7. Write a modified selection sort that sorts in descending order.
Q8. You have 5 numbers to sort. Which sort would you use and why?
Q9. Count the number of comparisons in insertion sort for input [1, 2, 3, 4, 5, 6, 7, 8].
Q10. If swap costs 10x more than comparison, which algorithm wins? What if comparison costs 10x more?
AnswersA1.pseudo[1, 3, 4, 1, 5, 9, 2, 6] (min=1 at idx 3, swap 3↔1) [1, 1, 4, 3, 5, 9, 2, 6] (min=1 at idx 3, swap 3↔1) [1, 1, 2, 3, 5, 9, 4, 6] (min=2 at idx 6, swap 4↔2) [1, 1, 2, 3, 5, 9, 4, 6] (min=3 at idx 3, already placed) [1, 1, 2, 3, 4, 9, 5, 6] (min=4 at idx 6, swap 5↔4) [1, 1, 2, 3, 4, 5, 9, 6] (min=5 at idx 6, swap 9↔5) [1, 1, 2, 3, 4, 5, 6, 9] (min=6 at idx 7, swap 9↔6)A2. Comparisons = 100×99/2 = 4,950. Swaps = at most 99.A3. Best: Already sorted → O(n). Worst: Reverse sorted → O(n²).A4. In each iteration i, we scan arr[i:] for the minimum. We then swap it into position i. After the swap, arr[i] contains the minimum of arr[i:], which is ≤ everything after it, and all previous positions already contain the global minimums from earlier passes.A5. After 3 iterations (i=1,2,3): [1, 2, 7, 9, 5, 3]. Elements [1,2,7,9] are sorted among themselves.A6. Insertion sort runs in O(n) on nearly sorted data because the inner while loop terminates almost immediately. Selection sort always takes O(n²).A7. Changeif arr[j] < arr[min_idx]toif arr[j] > arr[min_idx].A8. Either. For n=5, both are O(25) operations — trivial. But insertion sort is slightly better for nearly sorted data.A9. 7 comparisons (one per element, since the while loop terminates immediately when arr[j] <= key).A10. If swaps are 10× more expensive: Selection sort wins (n-1 swaps vs O(n²) shifts). If comparisons are 10× more expensive: Both make ~n²/2 comparisons, but insertion sort may do fewer if data is nearly sorted. Join Discord Previous04. Searching Algorithms — Linear Search & Binary SearchNext06. Merge Sort — The O(n log n) Breakthrough