Simple Sorts

Sort by hand the way people do: insertion, selection and bubble sort. See why they cost O(n²), when insertion sort is fast, and what a stable sort is.

16 minLesson 1 of 7
On this page
  1. The idea: grow a sorted part
  2. Insertion sort, traced
  3. Selection sort and bubble sort
  4. Why they are O(n²)
  5. Stable sorts keep ties in order
  6. Common mistakes
  7. Check yourself
  8. What to practise next

A friend hands you five numbered books to put back on a shelf: 7, 3, 9, 4, 1. You do not need an algorithm for that. You take the books one at a time, and each new one slides left past the bigger numbers until it sits in the right spot. In a few seconds the shelf reads 1, 3, 4, 7, 9.

That habit is a real sorting algorithm, called insertion sort. It and its two cousins, selection sort and bubble sort, are the "simple sorts". They are slow on big inputs. But they are short, they need no extra memory, and one of them still runs inside the built-in sort of Python and JavaScript.

The idea: grow a sorted part

All three simple sorts split the array into two parts: a sorted part at the front and an unsorted part after it. Each round moves one item from the unsorted part into the sorted part. After n rounds, everything is sorted.

They differ only in how they pick and place that item:

  • Insertion sort takes the next item, whatever it is, and slides it left into the right place among the sorted items.
  • Selection sort searches the unsorted part for the smallest item and swaps it to the end of the sorted part.
  • Bubble sort walks through the unsorted part swapping neighbours that are out of order, which carries the largest item to the end.
3 7 9 4 1 sorted part unsorted part 4 slides past 9 and 7
Insertion sort halfway through: the front is sorted, and the next item slides left until it fits.

Insertion sort, traced

Here is the bookshelf as an array. Watch the variable key: it holds the book in your hand while the bigger books shift one slot to the right to make a gap.

Insertion sort on [7, 3, 9, 4, 1]Use the arrows or ← → keys
for i from 1 to n - 1:  key = a[i]  j = i - 1  while j >= 0 and a[j] > key:    a[j + 1] = a[j]    j = j - 1  a[j + 1] = key
  1. a
    07
    13i
    29
    34
    41

    1A single item is already sorted, so the sorted part starts as just 7. The first item to insert is at i = 1.

  2. a
    07j
    13i
    29
    34
    41
    key
    3

    2Pick up 3 as the key. Compare it with its left neighbour, 7.

  3. a
    j
    07
    17i
    29
    34
    41
    key
    3

    37 is bigger than 3, so 7 shifts one slot right. 7 now shows twice, which is fine: the 3 is safe in key.

  4. a
    03
    17i
    29
    34
    41
    key
    3

    4j fell off the front, so the gap is at slot 0. Drop 3 there. The sorted part is now 3, 7.

  5. a
    03
    17j
    29i
    34
    41
    key
    9

    5Pick up 9. Its left neighbour 7 is not bigger, so 9 is already in place. One comparison, no moves.

  6. a
    03
    17j
    29
    39i
    41
    key
    4

    6Pick up 4. 9 is bigger, so 9 shifts right.

  7. a
    03j
    17
    27
    39i
    41
    key
    4

    77 is bigger than 4 too, so 7 shifts right.

  8. a
    03j
    17
    27
    39i
    41
    key
    4

    83 is not bigger than 4. Stop. The gap is just right of j, at slot 1.

  9. a
    03
    14
    27
    39
    41i
    key
    4

    9Drop 4 into slot 1. Four items sorted.

  10. a
    03
    14
    27j
    39
    49i
    key
    1

    10Pick up 1. 9 is bigger, so 9 shifts right.

  11. a
    03
    14j
    27
    37
    49i
    key
    1

    117 is bigger than 1, so 7 shifts right.

  12. a
    03j
    14
    24
    37
    49i
    key
    1

    124 is bigger than 1, so 4 shifts right.

  13. a
    j
    03
    13
    24
    37
    49i
    key
    1

    133 is bigger too. It shifts right, and j falls off the front. 1 is the smallest item so far.

  14. a
    01
    13
    24
    37
    49
    key
    1

    14Drop 1 into slot 0. The whole array is sorted.

  15. a
    01
    13
    24
    37
    49
    shifts
    7
    comparisons
    9

    157 shifts and 9 comparisons in total. The 7 shifts are exactly the 7 pairs that started in the wrong order, like (7, 3) and (9, 4). Fewer wrong pairs, less work.

1 / 15

The last step is the most useful fact about insertion sort. Each shift fixes exactly one pair that was out of order. An array that is already sorted has no such pairs, so insertion sort just does one comparison per item and finishes in O(n). An array that is almost sorted is almost that fast.

Insertion sort
function insertionSort(a) {
  for (let i = 1; i < a.length; i++) {
    const key = a[i];
    let j = i - 1;
    // shift bigger items one slot right to open a gap
    while (j >= 0 && a[j] > key) {
      a[j + 1] = a[j];
      j--;
    }
    a[j + 1] = key; // the gap is just right of j
  }
  return a;
}
 
insertionSort([7, 3, 9, 4, 1]); // [1, 3, 4, 7, 9]
def insertion_sort(a):
    for i in range(1, len(a)):
        key = a[i]
        j = i - 1
        # shift bigger items one slot right to open a gap
        while j >= 0 and a[j] > key:
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = key  # the gap is just right of j
    return a
 
insertion_sort([7, 3, 9, 4, 1])  # [1, 3, 4, 7, 9]

Selection sort and bubble sort

Selection sort scans the whole unsorted part to find the smallest item, then makes one swap. So it writes very little: at most one swap per round. But the scan always runs to the end, so it is no faster on sorted input than on shuffled input.

Bubble sort compares neighbours and swaps them when they are out of order. One pass carries the largest item to the end, like a bubble rising. If a pass makes no swaps, the array is sorted and you can stop early. It is easy to explain and almost never the right choice. It makes one swap per out-of-order pair, the same count as insertion sort's shifts, but a swap writes two slots where a shift writes one. And each pass compares every neighbouring pair it walks over, while insertion sort stops comparing as soon as an item fits.

Selection sort and bubble sort
function selectionSort(a) {
  for (let i = 0; i < a.length - 1; i++) {
    let min = i;
    for (let j = i + 1; j < a.length; j++) {
      if (a[j] < a[min]) min = j;
    }
    [a[i], a[min]] = [a[min], a[i]]; // one swap per round
  }
  return a;
}
 
function bubbleSort(a) {
  for (let end = a.length - 1; end > 0; end--) {
    let swapped = false;
    for (let j = 0; j < end; j++) {
      if (a[j] > a[j + 1]) {
        [a[j], a[j + 1]] = [a[j + 1], a[j]];
        swapped = true;
      }
    }
    if (!swapped) break; // no swaps: already sorted
  }
  return a;
}
def selection_sort(a):
    for i in range(len(a) - 1):
        smallest = i
        for j in range(i + 1, len(a)):
            if a[j] < a[smallest]:
                smallest = j
        a[i], a[smallest] = a[smallest], a[i]  # one swap per round
    return a
 
def bubble_sort(a):
    for end in range(len(a) - 1, 0, -1):
        swapped = False
        for j in range(end):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swapped = True
        if not swapped:  # no swaps: already sorted
            break
    return a

Why they are O(n²)

In the worst case, the array is in reverse order. Insertion sort then shifts the second item 1 slot, the third item 2 slots, and so on up to n − 1. That is 1 + 2 + … + (n − 1) = n(n − 1) / 2 shifts, which is O(n²). Selection sort always does that many comparisons, whatever the input. With 10,000 items that is about 50 million steps; with a million items, about 500 billion.

None of them need a second array. They move items around inside the one they were given, so the extra space is O(1). An algorithm like that is called in place.

ApproachTimeSpaceWhy
Insertion sortO(n²)O(1)O(n) on sorted or nearly sorted input. Stable.
Selection sortO(n²)O(1)Always n(n − 1)/2 comparisons, but at most n − 1 swaps. Not stable.
Bubble sortO(n²)O(1)O(n) on sorted input with the early stop. Stable.

Stable sorts keep ties in order

Real data has more than one field. Say a teacher's list is already in alphabetical order, and now she sorts it by score: Ann 82, Ben 75, Cal 82, Dee 75. A stable sort keeps items with equal keys in their original order, so the result is Ben 75, Dee 75, Ann 82, Cal 82. Within each score, names are still alphabetical.

Insertion sort is stable because it only shifts an item past items that are strictly bigger (a[j] > key). Selection sort is not, because its long-distance swap can jump an item over an equal one. On Ann 82, Ben 75, Cal 82, Dee 75, the first round swaps Ben to the front. The second round finds Dee and swaps her with Ann, which throws Ann to the very end, behind Cal. The result is Ben 75, Dee 75, Cal 82, Ann 82: the 82s are out of alphabetical order.

Common mistakes

Check yourself

Quick check

You run insertion sort on an array of 1,000 items that is already sorted. About how many comparisons does it make?

Orders sorted by time: (A, $5), (B, $3), (C, $5). You sort by price with a stable sort. What is the result?

Which simple sort does the fewest swaps on a reversed array of 6 items?

What to practise next

O(n²) is too slow for big inputs. Merge Sort gets to O(n log n) by splitting the array in half, sorting each half, and merging the two sorted halves, and it stays stable. Trace insertion sort by hand on [5, 1, 4, 2] before you move on: you should count 4 shifts.