← View series: problem solving with dsa
~/blog
Insertion Sort
Given an unsorted array, rearrange it in ascending order. Insertion sort builds the sorted portion one element at a time. It picks the first element from the unsorted portion, shifts larger elements in the sorted portion right by one, and inserts the picked element into the gap.
This is exactly how you sort a hand of playing cards: you pick up one card at a time and insert it where it belongs among the cards you already hold.
One Deep Walkthrough: [8, 5, 2, 9, 1]
The first element (8) is trivially sorted. We pick from index 1 onward.
Step 1 — pick key = arr[1] = 5
The sorted portion is [8]. Compare 5 with 8. Since 8 > 5, shift 8 right by one position.
| Array state | Action |
|---|---|
| [8, 5, 2, 9, 1] | key = 5. Compare 8 > 5 → shift 8 to index 1 |
| [8, 8, 2, 9, 1] | Sorted portion exhausted. Insert key at index 0 |
| [5, 8, 2, 9, 1] | Sorted: [5, 8]. Unsorted: [2, 9, 1] |
Step 2 — pick key = arr[2] = 2
Compare with 8 → shift. Compare with 5 → shift.
| Array state | Action |
|---|---|
| [5, 8, 2, 9, 1] | key = 2. Compare 8 > 2 → shift 8 to index 2 |
| [5, 8, 8, 9, 1] | Compare 5 > 2 → shift 5 to index 1 |
| [5, 5, 8, 9, 1] | Sorted portion exhausted. Insert key at index 0 |
| [2, 5, 8, 9, 1] | Sorted: [2, 5, 8]. Unsorted: [9, 1] |
Step 3 — pick key = arr[3] = 9
Compare with 8. 8 > 9? No. Stop immediately. Insert key at index 3 (its original position).
| Array state | Action |
|---|---|
| [2, 5, 8, 9, 1] | key = 9. Compare 8 > 9? No. Stop. Insert at index 3 |
| [2, 5, 8, 9, 1] | Sorted: [2, 5, 8, 9]. Unsorted: [1] |
Step 4 — pick key = arr[4] = 1
Compare with 9 → shift. Compare with 8 → shift. Compare with 5 → shift. Compare with 2 → shift.
| Array state | Action |
|---|---|
| [2, 5, 8, 9, 1] | key = 1. Compare 9 > 1 → shift 9 to index 4 |
| [2, 5, 8, 9, 9] | Compare 8 > 1 → shift 8 to index 3 |
| [2, 5, 8, 8, 9] | Compare 5 > 1 → shift 5 to index 2 |
| [2, 5, 5, 8, 9] | Compare 2 > 1 → shift 2 to index 1 |
| [2, 2, 5, 8, 9] | Sorted portion exhausted. Insert key at index 0 |
| [1, 2, 5, 8, 9] | Done |
Code
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arrLine by line:
for i in range(1, n):— start from index 1. Index 0 is the initial sorted portion.key = arr[i]— save the element to be inserted. Without saving it, the shifts would overwrite it.j = i - 1— start comparing from the rightmost element of the sorted portion.while j >= 0 and arr[j] > key:— shift condition. If the sorted element is greater thankey, it must move right. Thej >= 0guard prevents accessing index -1.arr[j + 1] = arr[j]— shift the element right by one position.j -= 1— move one position left to check the next sorted element.arr[j + 1] = key— insertkeyat the gap.j + 1is correct becausejwas decremented after the last shift (or never moved if no shift occurred).
Edge case — already sorted. The while condition arr[j] > key is always false. The inner loop never executes. arr[j + 1] = key writes key back to its own position (no-op). The array is scanned once. O(n).
Edge case — all identical. arr[j] > key is never true (equal is not greater). Zero shifts. O(n).
Time: O(n²) average and worst. O(n) best. Space: O(1).
Stability: Insertion sort is stable — equal elements keep their relative order because the shift stops at > (strictly greater), not >=.
Code Trace
Tracing insertion_sort([8, 5, 2, 9, 1]):
| i | key | j | while condition | Action | Array state |
|---|---|---|---|---|---|
| 1 | 5 | 0 | 0 ≥ 0 and 8 > 5 → True | Shift 8 to index 1 | [8, 8, 2, 9, 1] |
| -1 | -1 ≥ 0 → False | Insert key at j+1=0 | [5, 8, 2, 9, 1] | ||
| 2 | 2 | 1 | 1 ≥ 0 and 8 > 2 → True | Shift 8 to index 2 | [5, 8, 8, 9, 1] |
| 0 | 0 ≥ 0 and 5 > 2 → True | Shift 5 to index 1 | [5, 5, 8, 9, 1] | ||
| -1 | -1 ≥ 0 → False | Insert key at index 0 | [2, 5, 8, 9, 1] | ||
| 3 | 9 | 2 | 2 ≥ 0 and 8 > 9 → False | Exit while, insert at j+1=3 | [2, 5, 8, 9, 1] |
| 4 | 1 | 3 | 3 ≥ 0 and 9 > 1 → True | Shift 9 to index 4 | [2, 5, 8, 9, 9] |
| 2 | 2 ≥ 0 and 8 > 1 → True | Shift 8 to index 3 | [2, 5, 8, 8, 9] | ||
| 1 | 1 ≥ 0 and 5 > 1 → True | Shift 5 to index 2 | [2, 5, 5, 8, 9] | ||
| 0 | 0 ≥ 0 and 2 > 1 → True | Shift 2 to index 1 | [2, 2, 5, 8, 9] | ||
| -1 | -1 ≥ 0 → False | Insert key at index 0 | [1, 2, 5, 8, 9] |
Insertion sort is the best O(n²) sort for nearly-sorted data. Python's Timsort uses it for small subarrays. It is adaptive (O(n) best case), stable, and has low overhead.
→ LeetCode 147: Insertion Sort List — the linked list version.