Pick up a hand of playing cards one at a time. Each new card you grab, you slide left along the cards you're already holding until it sits in the right spot, then you grab the next one. The cards in your hand stay sorted the whole time, and the pile on the table shrinks. That is the entire algorithm — insertion sort is just "sort your hand as you deal."
Let's name the pieces. The sorted region is the part of the array on the left that's already in order (the cards in your hand). The key is the next item you pull from the unsorted pile. To place the key, you compare it to the sorted items from right to left; every item bigger than the key shifts one slot to the right to open a gap, and the key drops into that gap. The simplest way to count the work is: each key does a few comparisons, plus one shift for every larger item it has to pass. If a hand of 5 cards is already in order, each new card just gets one quick peek and stays put — that's only $4$ comparisons and $0$ shifts. Easy hands are cheap; messy hands are dear.
To be precise, for an array of size $n$ the number of comparisons runs from a best case of $n-1$ (already sorted) up to a worst case of $\frac{n(n-1)}{2}$ (reverse sorted). The real cost driver is the inversion count $I$ — the number of out-of-order pairs in the input. Insertion sort performs exactly one shift per inversion, so total shifts $= I$ and the running time is $T = \mathcal{O}(n + I)$. That single formula explains everything: a nearly-sorted array has $I = \mathcal{O}(n)$ inversions, so the sort finishes in near-linear time, while a reverse-sorted array has the maximum $I = \frac{n(n-1)}{2}$ and the sort degrades to $\mathcal{O}(n^2)$. On the sliders above, Input Size sets $n$ and the Preset menu sets how scrambled the input is; the Comparisons and Shifts cards count the work live.
Try this in the sim above:
1. Set the Preset to Already sorted and step through — watch Shifts stay at $0$ while Comparisons climb only to $n-1$. That's the $\mathcal{O}(n)$ best case.
2. Switch to Reverse sorted — every key has to slide all the way left, and Shifts climbs to the maximum $\frac{n(n-1)}{2}$. That's the $\mathcal{O}(n^2)$ worst case.
3. Pick Nearly sorted and compare the totals to a random array of the same $n$ — almost no shifts, because there are only a handful of inversions to fix. That's why Timsort hunts for nearly-sorted runs.
Insertion Sort models the way a card player sorts a hand of cards: pick up each card and slide it into the correct position among the already-sorted cards in hand. It is one of the most practically useful simple sorting algorithms, forming the basis of Timsort (Python, Java's Arrays.sort for small arrays) and Shell Sort. For nearly-sorted arrays and small n, it is often the fastest algorithm in practice due to its minimal overhead and excellent cache behavior.
| Symbol | Meaning | Range |
|---|---|---|
| \(n\) | Input array size | \(\mathbb{Z}^+\) |
| \(T(n)\) | Number of comparisons | \([n-1,\ \frac{n(n-1)}{2}]\) |
| \(I\) | Number of inversions in input | \([0,\ \frac{n(n-1)}{2}]\) |
| \(\text{key}\) | Current element being inserted | \(A[i]\) |
| \(i\) | Outer loop: current key index | \(1 \leq i \leq n-1\) |
| \(j\) | Inner loop: scan back position | \(-1 \leq j \leq i-1\) |
| \(S(n)\) | Number of shifts = number of inversions | \([0,\ \frac{n(n-1)}{2}]\) |
Each shift in the inner loop corrects exactly one inversion. Therefore the total number of shifts (and comparisons within 1) equals the number of inversions \(I\) in the input permutation:
$$\text{Shifts} = I, \quad \text{Comparisons} \in [I, I + (n-1)]$$If the array is sorted, every key is already in place. The inner while-loop exits immediately. Only \(n-1\) comparisons are made (one per outer loop iteration):
$$B(n) = n-1 \implies \mathcal{O}(n)$$Reverse-sorted input has \(\binom{n}{2} = \frac{n(n-1)}{2}\) inversions. Key at position \(i\) must shift all \(i\) elements before it:
$$W(n) = \sum_{i=1}^{n-1} i = \frac{n(n-1)}{2} = \Theta(n^2)$$Expected number of inversions in a random permutation of \(n\) elements is \(\binom{n}{2}/2 = \frac{n(n-1)}{4}\):
$$A(n) = \frac{n(n-1)}{4} = \Theta(n^2)$$Insertion Sort is input-sensitive — its runtime depends on the degree of sortedness. For arrays with \(\mathcal{O}(n)\) inversions (nearly sorted), it runs in \(\mathcal{O}(n)\). This is the formal reason Timsort uses Insertion Sort on small nearly-sorted runs.
$$T = \mathcal{O}(n + I) \quad \text{where } I = \text{number of inversions}$$Insertion Sort is stable: the condition A[j] > key (strict >) ensures equal elements are not shifted, preserving their relative order.
Use binary search to find the insertion position in O(log i) comparisons per key, reducing total comparisons to \(\mathcal{O}(n \log n)\). However, shifts remain \(\mathcal{O}(n^2)\) in the worst case — so overall complexity stays \(\mathcal{O}(n^2)\) unless combined with a linked structure.
Array: [5, 3, 8, 1, 9, 2]
i=1, key=3: Compare 3 vs 5 → shift 5 right → [5,5,8,1,9,2] → insert 3 → [3,5,8,1,9,2]. Shifts:1.
i=2, key=8: Compare 8 vs 5 → no shift (8>5). Insert in place → [3,5,8,1,9,2]. Shifts:0.
i=3, key=1: Compare vs 8→shift; vs 5→shift; vs 3→shift → [3,5,8,8,9,2]→[3,5,5,8,9,2]→[3,3,5,8,9,2]. Insert → [1,3,5,8,9,2]. Shifts:3.
i=4, key=9: Compare vs 8 → no shift. → [1,3,5,8,9,2]. Shifts:0.
i=5, key=2: Compare vs 9,8,5,3,1 → shift 4 elements → insert → [1,2,3,5,8,9]. Shifts:4.
Final: [1,2,3,5,8,9]. Total comparisons: 11. Total shifts: 8 (equal to the 8 inversions in the input).
> (not ≥) in the while condition is unconditionally stable. The inner loop stops when A[j] ≤ key, so equal elements are never shifted past each other. Using ≥ instead of > breaks stability. This is an important detail: the standard textbook algorithm as written in CLRS is stable by definition, not by accident.for i in range(n).A[j] >= key instead of A[j] > key in the while condition.>. Using ≥ makes the sort unstable by shifting equal elements, and also performs one extra unnecessary comparison at the correct insertion point. For the standard stable sort behavior used in production (Timsort requires stability), always use strict greater-than.A[j+1] = key — confusing j's final value.A[j+1]. A common error is writing A[j] = key (off by one) or incrementing j before placing the key, shifting it by an extra position and corrupting the sorted order.