Insertion Sort
Intuition
Insertion sort sorts the array the way most people sort a hand of
playing cards. Start with the leftmost card alone — a one-element
"sorted hand". Pick up the next card, slide it backwards past every
card that's larger, and drop it in its proper place. The sorted hand
on the left grows by one each pick. After n − 1 picks the whole
array is sorted.
The single concept that makes it click: each pass takes one new value (the key) and inserts it into a region that is already sorted.
How It Works
One outer loop over the array, one inner loop that walks backwards:
- Outer loop runs
ifrom1ton − 1. Slotiis the new card being picked up; positions0..i − 1are the already-sorted hand. - Lift the key. Read
key = arr[i]and remember it. The slot atarr[i]is now logically empty — its old value lives inkey. - Inner loop runs
jfromi − 1down toward0, but stops as soon asarr[j] <= key. Every iteration shiftsarr[j]one slot to the right (arr[j + 1] = arr[j]) — making room forkey— and decrementsj. - Place the key. When the inner loop exits,
arr[j + 1]is the empty slot wherekeybelongs. Writearr[j + 1] = key.
The key invariant: at the top of each outer iteration, arr[0..i − 1]
is sorted. The inner loop preserves that invariant by carving out the
correct insertion point.
Step By Step
For the default input [38, 12, 56, 7, 41, 23, 19]:
- Pass
i = 1.key = 12. Sorted hand is[38]. Compare38 > 12→ shift38right.jbecomes−1. Placekeyat index 0 →[12, 38, 56, 7, 41, 23, 19]. - Pass
i = 2.key = 56.38 < 56→ loop exits immediately.56stays where it is. - Pass
i = 3.key = 7. Shift56,38,12one slot right in turn untilj = −1. Place7at index 0 →[7, 12, 38, 56, 41, 23, 19]. - Pass
i = 4.key = 41.56 > 41→ shift56.38 < 41→ stop. Place41→[7, 12, 38, 41, 56, 23, 19]. - Pass
i = 5.key = 23. Shift56,41,38→ place at index 2 →[7, 12, 23, 38, 41, 56, 19]. - Pass
i = 6.key = 19. Shift56,41,38,23→ place at index 2 →[7, 12, 19, 23, 38, 41, 56]. Done.
In the visualization, watch the bar at position i lift out of the
row and float above as the key. The cursor j walks left, and
each shifted bar slides one slot rightward to fill the gap. When j
finds a bar smaller than the key (or runs off the left edge), the
floating key drops back into the row at j + 1.
Complexity
| Case | Time |
|---|---|
| Best (already sorted) | O(n) |
| Average | O(n²) |
| Worst (reverse sorted) | O(n²) |
Space: O(1) — in place.
Insertion sort is adaptive: the inner loop short-circuits the
moment arr[j] <= key. On a nearly-sorted array the inner loop
almost never runs, so the total cost approaches O(n). This is why
real libraries (Java's Arrays.sort for primitives, Python's Timsort)
fall back to insertion sort for short subarrays and final cleanup
passes — it's optimal on the inputs the divide-and-conquer phases
hand it.
Edge Cases
- Empty or one-element array — outer loop runs zero times.
- Already sorted input — every inner loop exits on the first
iteration. One comparison per outer pass →
O(n)total. This is the case that makes insertion sort a real choice for "almost sorted" data. - All equal values —
arr[j] > keyis always false, inner loop never runs.O(n)total. - Duplicates — insertion sort is stable because the inner
loop uses strict
>. An equal value never crosses past another equal value, so original relative order is preserved.
Common Mistakes
- Using
>=instead of>in the inner-loop guard. That breaks stability — equal values get shifted past each other unnecessarily. - Forgetting the off-by-one when placing the key. After the loop,
arr[j]is the slot that passed the test (orj = −1). The key belongs atarr[j + 1], notarr[j]. - Overwriting before remembering. Without lifting the value into
keyfirst, the very first shift overwrites it. The "key holds the value we're inserting" step is non-optional. - Shifting one cell too few. Inside the loop, the assignment is
arr[j + 1] = arr[j]— the value atjmoves up, leavingjconceptually empty for the next iteration to overwrite. Writingarr[j] = arr[j - 1]shifts the wrong direction relative to where the key will land.