DAY 8 / 30 · WEEK 2 · PART 3 OF 3

Insertion Sort

Part 1: Bubble Sort · Part 2: Selection Sort · Insertion Sort (here)

Goal of the day

Concept

This is how most people actually sort a hand of playing cards: pick up one new card at a time, and slide it into its correct spot among the cards you've already arranged. Everything to the left of the card you're holding is already sorted — you just need to find where the new one fits, shifting bigger cards one slot to the right to make room, and dropping the new card into the gap. Repeat until every card has been picked up once.

Diagram

Grow a sorted prefix, one element at a time 2 5 1 8 9 ← "1" being inserted into [2,5], the sorted prefix No swapping — 2 and 5 both shift one slot right to make room, then 1 drops into the freed-up gap at index 0.

Interactive visualization

key being inserted shifting right slot just freed sorted prefix
Press Play or Step to begin.

The code

function insertionSort(arr) {
  for (let i = 1; i < arr.length; i++) {
    const key = arr[i];
    let j = i - 1;
    while (j >= 0 && arr[j] > key) {
      arr[j + 1] = arr[j]; // shift bigger elements right
      j--;
    }
    arr[j + 1] = key; // drop the key into its now-open slot
  }
  return arr;
}

Line by line:

Dry run: insertionSort([5, 2, 8, 1])

ikeyShiftsArray after
125 shifts right[2, 5, 8, 1]
28none — 8 > 5 is false immediately[2, 5, 8, 1]
318, 5, 2 all shift right[1, 2, 5, 8]

i=2 needed zero shifts because 8 was already bigger than everything to its left — that's the O(n) best-case behavior showing up even mid-array, not just for a fully-sorted input.

Complexity

Time (best)Time (worst)SpaceStable?
O(n) — already sorted, no shiftsO(n²)O(1)Yes

Of Day 8's three sorts, insertion sort is the one real sorting libraries actually reuse — many O(n log n) sorts (including some Day 10 merge sort implementations) switch to insertion sort for small sub-arrays, since its low constant factor and O(n) best case make it faster in practice below a certain size.

Interview corner

How to talk through it out loud: state the invariant — "after step i, the first i elements are fully sorted relative to each other." Then connect it to the best case explicitly: "if the array is already sorted, this invariant is trivially maintained with zero shifts per step, which is where the O(n) best case comes from."

Practice problems

1. Search Insert Position (Easy — LeetCode: Search Insert Position)

Given a sorted array and a target, return the index where the target is found, or where it would be inserted to keep the array sorted.

2. Remove Element (Easy — LeetCode: Remove Element)

Remove all occurrences of a value from an array in place, and return the new length.

3. Insertion Sort List (Medium — LeetCode: Insertion Sort List)

Sort a linked list using insertion sort. (A preview — we build linked lists properly on Day 15. For now, just think through the approach in terms of arrays.)

Quiz

1. Which sort has the best BEST-CASE time complexity on a nearly-sorted array?

2. How does insertion sort move elements out of the way?

3. Is insertion sort stable?

4. Where do real sorting libraries often still use insertion sort?

5. How does insertion sort choose which element to place next, compared to selection sort?