Insertion sort: shift the larger elements right, insert into the hole
Insertion sort sorts in place by walking left-to-right and inserting each element into its correct position among the elements already processed. Where selection sort finds the minimum of the suffix and swaps once per pass, insertion sort takes the next unprocessed element and slides it down through the sorted prefix until it lands in the right place.
public static void insertionSort(final int[] xs) {
for (int i = 1; i < xs.length; i++) {
final int current = xs[i]; // save the value to insert
int j = i - 1;
while (j >= 0 && xs[j] > current) { // shift larger elements right
xs[j + 1] = xs[j];
j--;
}
xs[j + 1] = current; // drop current into the hole
}
}
Three structural commitments distinguish this from selection sort:
1. The outer loop's i is "the next element to insert." i starts at 1 because xs[0] is trivially a sorted prefix of length 1. At the start of iteration i, the prefix xs[0..i-1] is sorted; at the end, the prefix xs[0..i] is sorted. 2. **The inner loop walks backward through the sorted prefix. Each comparison xs[j] > current checks whether the element at position j should be shifted right to make room for current. The loop continues while the prefix still has larger elements; it stops when j runs off the left edge (j < 0) or when xs[j] is no longer larger than current. 3. The element being inserted is saved in current before the shifting starts.** Without the save, the first shift xs[j+1] = xs[j] would clobber xs[i] (the value to insert): the same destructive-write problem as the swap that skips its temporary variable.
Why no swap
A naive presentation of insertion sort uses repeated swaps: slide current left one position at a time by swapping with its neighbor. That works and is sometimes pedagogically clearer, but it costs three assignments per shift (the temp-swap) versus one (xs[j+1] = xs[j]). For 1 000 000 elements the difference is real. The CSCD 210 lecture style teaches the shift-and-insert form because it is the form java.util.Arrays.sort uses internally (in its small-subarray path).
Best-case vs worst-case
Insertion sort is the rare O(n²) algorithm with a meaningful best case:
- Best case (already sorted): the inner loop's condition
xs[j] > currentis false on the very first check, so it makes one comparison and no shifts. Total work:n - 1comparisons,O(n). - Worst case (reverse sorted): every new element has to shift past every prior element. Total work:
n(n-1)/2,O(n²).
This is why insertion sort is the right choice for nearly-sorted data, and why hybrid sorts like Timsort fall back to insertion sort for small or partially-sorted subarrays.
In other languages
- C: identical algorithm; identical shift-and-insert idiom.
- Python:
sorted(xs)uses Timsort which falls back to insertion sort for small subarrays. The pattern is internal. - Rust:
slice::sortuses pattern-defeating quicksort with insertion-sort fallback.