Insertion Sort
Slide each new element into its place in the sorted prefix — the quadratic sort that production code actually uses.
Hands on Interactive visualization
Implementation Code
export function insertionSort(a: number[]): number[] {
for (let i = 1; i < a.length; i++) {
const key = a[i]; // element to place into the sorted prefix
let j = i - 1;
// Shift larger elements one slot right (strict > keeps stability).
while (j >= 0 && a[j] > key) {
a[j + 1] = a[j];
j--;
}
a[j + 1] = key; // drop the key into the gap
}
return a;
}Big-O Complexity
Runtime is O(n + d) where d is the number of inversions — near-linear on nearly-sorted data.
In production When to use
Nearly every production hybrid sort — TimSort (Python, Java objects, V8), pdqsort (Go, Rust unstable), introsort (C++) — switches to insertion sort for small partitions, typically under 16–32 elements.
Per-frame lists (sprites by depth, entities by distance) barely change between frames. Insertion sort on the previous frame's order runs in near-linear time.
It is a true online algorithm: elements can arrive one at a time and each is placed as it lands, keeping the collection sorted at every moment.
O(1) memory, no recursion, and a tiny code footprint make it the default for sorting small buffers on allocator-free firmware.
Insertion into small sorted runs and node-level ordering in B-tree pages amounts to insertion sort: shift the tail, drop in the new key.
Maintaining a small sorted list under interactive edits (adding a row to a sorted table) is exactly one insertion pass — no need to re-sort the whole array.
Trade-offs Strengths & weaknesses
- Adaptive: cost scales with the number of inversions, so nearly-sorted input is near-linear.
- Stable — equal keys never cross each other.
- Online: can sort a stream as elements arrive.
- In-place, O(1) memory, tiny code size.
- Very low constant factors: the fastest sort in practice below roughly 20–30 elements.
- Shift-based inner loop does one write per moved element, unlike bubble sort's swap pairs.
- O(n²) average and worst case — unusable as a general sort at scale.
- Reverse-sorted input is its pathological case: maximal shifts every step.
- Each insertion is sequential; the algorithm offers little parallelism.
- Shifting large records is expensive — indirection (sorting pointers/indices) is needed for wide payloads.
Intuition Mental model
Sorting a hand of playing cards
Exactly how most people sort cards dealt one at a time: everything in your hand stays sorted, and each new card slides in where it belongs.
- Your left hand holds the cards received so far, already in order.
- Pick up the next card (the key) and hold it — its old slot is now free.
- Scan right-to-left through your hand, sliding larger cards one slot over.
- Drop the new card into the gap that opens where it fits.
- The hand is sorted after every single card — that is the online property.
Prep Interview questions
Why do industrial-strength sorts like TimSort and introsort switch to insertion sort for small subarrays?
Express insertion sort's runtime in terms of inversions.
Why shift elements instead of swapping like bubble sort?
Could you speed up finding the insertion point with binary search? Does it change the complexity?
What makes insertion sort 'online' and why does selection sort fail that test?
Watch out Common bugs
The prefix of length 1 is already sorted; starting at 0 wastes a pass at best, and in while-loop formulations can underflow the inner index.
Shifting while a[j] >= key moves equal elements past each other, silently breaking stability — invisible with ints, a real bug when sorting records by key.
After the while loop, the key belongs at j + 1 (or j in the 'j starts at i' formulation). Off-by-one here overwrites a neighbor and drops an element.
In Rust/C with unsigned indices, the condition j >= 0 is always true. Compare j > 0 and index a[j - 1], or the loop wraps around and panics.
One screen Cheat sheet
Grow a sorted prefix; shift bigger elements right and drop each new key into the gap.
- Stable, in-place, adaptive, online — the full house of nice properties.
- Runtime is O(n + inversions): great on nearly-sorted data.
- The small-array workhorse inside TimSort, pdqsort, and introsort.
- Shift (one write) beats swap (three writes) in the inner loop.
- Strict > in the shift condition is what preserves stability.
- Binary search for the position helps comparisons, not the O(n²) shifts.