DSA · Sorting
Insertion sort, one card at a time
Lift the next value out, slide the bigger ones right, drop it into the gap. Stable, in place, and the fastest simple sort on nearly sorted data.
Step through 5 1 4 2 — shift vs swap →01 The idea
You already know this one — it is how you sort a hand of cards
Pick up a hand of playing cards one at a time. The cards already in your hand are in order. When a new card arrives you run your eye leftwards along the hand, find the spot where it fits, push the bigger cards a little to the right, and slot it in. You never look at the cards further left, because they are already smaller than the one that stopped you.
Insertion sort does exactly that to an array. It keeps a sorted prefix at the front and grows it by one slot per round. The value sitting at the boundary is lifted out into a variable, the bigger values in the prefix slide one place right to open a gap, and the lifted value drops into that gap. After n-1 rounds the prefix is the whole array.
The important word is slide. Most people write this with swaps the first time, because bubble sort taught them to swap. Swapping sorts correctly, but it pays two array writes for every one place a value moves. Section 03 puts the two versions side by side and counts.
02 Hand trace
Sorting 5 1 4 2 by hand
Four values, three rounds. Slot 0 counts as a sorted prefix of one, so round 1 starts at slot 1. Follow 5 as you read down: it never jumps, it only ever slides one slot right at a time. A dot marks the gap — the slot whose value is currently being held in the key variable.
Count the work. Round 1 made 1 comparison, round 2 made 2, round 3 made 3, so 6 comparisons in total. Four slides happened — 5 slid three times and 4 slid once — and three keys were dropped into gaps, which is 4 + 3 = 7 array writes. Be honest about what this list costs: round i can never make more than i comparisons, so 6 is the most four values can possibly cost. 5 1 4 2 is a worst case for comparisons.
Look at where each scan stopped, though. Rounds 2 and 3 both halted on the 1 in slot 0 instead of running off the left edge. On this list that saved nothing, because slot 0 was the last slot either way — but the rule that stopped them is the one that matters. The instant a value is not bigger than the key, everything further left is smaller than it and therefore smaller than the key, so there is nothing left to check. On data that is already close to sorted the scans halt near the right-hand end and most of the prefix is never touched: 1 2 4 3 5 costs 5 comparisons and 1 slide, where the maximum for five values is 10 of each. That early exit is the thing the code in section 03 has to encode.
03 Mechanics
Swap every step, or lift the key and slide
Both versions sort correctly and both make exactly the same comparisons on any input. They differ only in how they move data. The left one drags a value leftwards by swapping it with its neighbour over and over. The right one takes the value out of the array first, so each move costs a single copy.
Naive · swap every step
for i = 1 to n-1: j = i while j > 0 and A[j-1] > A[j]: swap A[j-1], A[j] // 2 writes j = j - 1
Improved · lift the key and shift
for i = 1 to n-1: key = A[i] // lift it out j = i - 1 while j >= 0 and A[j] > key: A[j+1] = A[j] // 1 write j = j - 1 A[j+1] = key // fill the gap
Why lift the key out first. Once key holds a private copy, slot i is free, so a value can be copied rightwards without saving whatever it lands on. That turns every one-place move from two writes into one. On 5 1 4 2 the totals are 7 writes against 8; on the reversed list 5 4 3 2 1 they are 14 against 20.
Why the scan is allowed to stop early. while j >= 0 and A[j] > key exits the instant it meets a value that is not bigger. The prefix is sorted, so everything further left is smaller than that value and therefore smaller than the key too. In the trace, rounds 2 and 3 both halted on the 1 in slot 0 — which on that particular list saves nothing, since slot 0 ended the scan anyway. Load the nearly sorted preset 1 2 4 3 5 in section 04 to watch it actually pay: 5 comparisons and 1 slide, against a maximum of 10 of each for five values.
Why the test is > and never >=. A value equal to the key stops the scan, so the key lands to its right. Equal values therefore never cross each other and keep their input order. Changing that one character to >= makes insertion sort unstable and adds shifts that buy nothing.
05 Cheat sheet
The numbers to have ready
| Case | Time | What the array looks like, and the exact count |
|---|---|---|
| Best — already sorted | O(n) | The inner test fails on its first try every round: n−1 comparisons and zero slides. |
| Average — random order | O(n²) | Each key travels about half the prefix: roughly n²/4 comparisons and n²/4 slides. |
| Worst — reverse sorted | O(n²) | Every key travels the whole prefix: n(n−1)/2 comparisons and the same number of slides. For n = 5 that is 10 and 10. |
| Extra space | O(1) | One variable for the key and one index. It sorts inside the original array. |
06 Where & why
When insertion sort is the right answer
Insertion sort is not a fast sort. Its value is that it is the cheapest thing to run when the data is already close to sorted, and the cheapest thing to run when there is very little data. Both of those situations turn up constantly inside faster algorithms, which is why this quadratic sort is still shipping in every standard library.
Reach for it when…
Use something else when…
| Situation | Better choice | Why |
|---|---|---|
| The list is large and in random order | Timsort or introsort — the built-in sort | At n = 10,000 an O(n log n) sort does around 1.3 × 10⁵ comparisons; insertion sort averages around 2.5 × 10⁷. |
| Writes are the expensive operation, as on flash or EEPROM | Selection sort | It performs at most n−1 swaps in total, while insertion sort can perform O(n²) writes. |
| You must guarantee O(n log n) in the worst case with O(1) space | Heap sort | No quadratic worst case and no second array, at the cost of stability and cache friendliness. |
| The data is huge but arrives in long sorted runs | Timsort | It detects those runs and merges them, and it uses binary insertion sort internally on the short pieces. |
07 Interview questions
Asked the way an interviewer asks them
Tap a question, say your answer out loud, then check it. Each answer is about twenty seconds of speech, which is the length an interviewer actually wants.
What is insertion sort, in one sentence?
What are the best, average and worst case time complexities?
Why is the best case O(n) and not O(n²)?
Is insertion sort stable? Prove it.
What does adaptive mean here, and exactly how adaptive is it?
Why shift instead of swap?
Does the shifting version always do fewer writes than the swapping one?
Insertion sort versus bubble sort versus selection sort — which do you pick?
Can you find the insertion point faster than a linear scan?
How many comparisons does it make on 5 1 4 2, and how many writes?
State the loop invariant you would give in an interview.
Where is insertion sort actually used in production?
08 Practice problems
Six problems, all solvable with this lesson alone
Do the first two on paper. The twist line tells you what each problem is really testing, so read it before you start writing code.