Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 5 – Sorting and selection

Selection, bubble and insertion sort

How selection, bubble and insertion sort work, their invariants, exact comparison and swap counts, and why insertion sort is fast on nearly sorted data.

  • Beginner
  • 25 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 7 of 25 minutes free
Free: Three ways to grow a sorted part · The three sorts traced on eight numbers · Exercise and self-check. With a pass: 3 in-depth parts and 3 interview answers, about 18 minutes.

What you will learn

  • Implement selection, bubble and insertion sort with their invariants
  • Count the comparisons and swaps of each sort for a given input
  • Explain why insertion sort is fast on nearly sorted data

Before you start

Selection sort, bubble sort and insertion sort are the three elementary sorts. Each one grows a sorted part of the array by one item per pass, using only comparisons and swaps or shifts, and each takes about n²/2 comparisons in the worst case. Selection sort finds the smallest remaining item and swaps it into place. Bubble sort walks the array and swaps neighbours that are out of order, so the largest remaining item sinks to the end. Insertion sort takes the next item and shifts the larger items of the sorted part one place right until the new item fits. They are worth knowing because they differ in what they save: selection sort makes few swaps, bubble sort and insertion sort stop early on sorted input, and insertion sort’s work is exactly the disorder of its input. This lesson traces all three on the same eight numbers with every comparison and swap counted.

Three ways to grow a sorted part

Each sort keeps an invariant, a statement about the array that is true after every pass:

  • Selection sort: after pass i, the first i places hold the i smallest items in order. Each pass compares every remaining item with the smallest found so far, so the comparisons are always n(n − 1)/2, while the swaps are at most n − 1, one per pass.
  • Bubble sort: after pass i, the last i places hold the i largest items in order. A pass that makes no swap proves that the array is sorted, so with that early exit sorted input costs only n − 1 comparisons.
  • Insertion sort: after pass i, the first i + 1 items of the input are sorted among themselves, although later items may still land between them. Each shift moves one larger item past the item being inserted.
The same 8 numbers after 3 passes of selection, bubble and insertion sort, each with the part its invariant describes.Input5, 2, 8, 1, 9, 3, 7, 4Selection sort, after 3 passes1, 2, 3 | 5, 9, 8, 7, 4the 3 smallest items, in their final placesBubble sort, after 3 passes1, 2, 3, 5, 4 | 7, 8, 9the 3 largest items, in their final placesInsertion sort, after 3 passes1, 2, 5, 8 | 9, 3, 7, 4the first 4 items, sorted among themselves

What each sort has finished after 3 passes

Text description of the diagram

All three sorts start from 5, 2, 8, 1, 9, 3, 7, 4. A bar separates the part that each sort's invariant describes from the rest.

  • Selection sort, after 3 passes: 1, 2, 3 | 5, 9, 8, 7, 4. The left part holds the 3 smallest items in their final places.
  • Bubble sort, after 3 passes: 1, 2, 3, 5, 4 | 7, 8, 9. The right part holds the 3 largest items in their final places; the left part happens to be almost sorted too.
  • Insertion sort, after 3 passes: 1, 2, 5, 8 | 9, 3, 7, 4. The left part holds the first 4 items of the input, sorted among themselves but not yet in their final places: 3 and 4 will still go between them.

Bubble sort and insertion sort only ever move an item past a strictly larger neighbour, so equal items keep their order and both sorts are stable. Selection sort’s swap can carry an item a long way, past items equal to it, so it is not stable in its usual array form.

The three sorts traced on eight numbers

This program runs all three sorts on 5, 2, 8, 1, 9, 3, 7, 4 and prints the array after every pass:

Selection, bubble and insertion sort, pass by pass Python · trace/three_sorts.py
"""Selection, bubble and insertion sort on the same 8 numbers, with every pass printed and the work counted."""


def selection_sort(a, show):
    a = list(a)
    comparisons = swaps = 0
    for i in range(len(a) - 1):
        smallest = i
        for j in range(i + 1, len(a)):
            comparisons += 1
            if a[j] < a[smallest]:
                smallest = j
        if smallest != i:
            a[i], a[smallest] = a[smallest], a[i]
            swaps += 1
        show(i + 1, a)
    return comparisons, swaps


def bubble_sort(a, show):
    a = list(a)
    comparisons = swaps = 0
    for end in range(len(a) - 1, 0, -1):
        swapped = False
        for j in range(end):
            comparisons += 1
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                swaps += 1
                swapped = True
        show(len(a) - end, a)
        if not swapped:  # a pass without a swap: the array is sorted
            break
    return comparisons, swaps


def insertion_sort(a, show):
    a = list(a)
    comparisons = shifts = 0
    for i in range(1, len(a)):
        item = a[i]
        j = i
        while j > 0:
            comparisons += 1
            if a[j - 1] <= item:
                break
            a[j] = a[j - 1]  # shift the larger item one place right
            shifts += 1
            j -= 1
        a[j] = item
        show(i, a)
    return comparisons, shifts


data = [5, 2, 8, 1, 9, 3, 7, 4]
print("input   ", data)
for sort, moves in [(selection_sort, "swaps"), (bubble_sort, "swaps"), (insertion_sort, "shifts")]:
    print(sort.__name__)
    comparisons, work = sort(data, lambda k, a: print(f"  pass {k}", a))
    print(f"  {comparisons} comparisons, {work} {moves}")

Output

input    [5, 2, 8, 1, 9, 3, 7, 4]
selection_sort
  pass 1 [1, 2, 8, 5, 9, 3, 7, 4]
  pass 2 [1, 2, 8, 5, 9, 3, 7, 4]
  pass 3 [1, 2, 3, 5, 9, 8, 7, 4]
  pass 4 [1, 2, 3, 4, 9, 8, 7, 5]
  pass 5 [1, 2, 3, 4, 5, 8, 7, 9]
  pass 6 [1, 2, 3, 4, 5, 7, 8, 9]
  pass 7 [1, 2, 3, 4, 5, 7, 8, 9]
  28 comparisons, 5 swaps
bubble_sort
  pass 1 [2, 5, 1, 8, 3, 7, 4, 9]
  pass 2 [2, 1, 5, 3, 7, 4, 8, 9]
  pass 3 [1, 2, 3, 5, 4, 7, 8, 9]
  pass 4 [1, 2, 3, 4, 5, 7, 8, 9]
  pass 5 [1, 2, 3, 4, 5, 7, 8, 9]
  25 comparisons, 13 swaps
insertion_sort
  pass 1 [2, 5, 8, 1, 9, 3, 7, 4]
  pass 2 [2, 5, 8, 1, 9, 3, 7, 4]
  pass 3 [1, 2, 5, 8, 9, 3, 7, 4]
  pass 4 [1, 2, 5, 8, 9, 3, 7, 4]
  pass 5 [1, 2, 3, 5, 8, 9, 7, 4]
  pass 6 [1, 2, 3, 5, 7, 8, 9, 4]
  pass 7 [1, 2, 3, 4, 5, 7, 8, 9]
  18 comparisons, 13 shifts

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 three_sorts.py

Selection sort made 28 comparisons, 7 + 6 + … + 1, and only 5 swaps: passes 2 and 7 found the smallest item already in place. Bubble sort made 13 swaps and stopped after pass 5, when a pass found nothing to swap. Insertion sort needed 18 comparisons and 13 shifts. The two 13s are not a coincidence: the input has exactly 13 inversions, pairs of items that stand in the wrong order, such as 5 before 2 or 9 before 3. Every bubble-sort swap and every insertion-sort shift puts exactly one such pair right.

The three side by side:

  • Selection sort: always n(n − 1)/2 comparisons, at most n − 1 swaps; not stable and not adaptive.
  • Bubble sort with an early exit: n − 1 comparisons on sorted input and n(n − 1)/2 at worst, one swap per inversion; stable and adaptive.
  • Insertion sort: n − 1 comparisons on sorted input and n(n − 1)/2 at worst, one shift per inversion; stable and adaptive.

Selection sort is the one to pick when every write is expensive, bubble sort is mostly a teaching tool, and insertion sort is the practical choice for short or nearly sorted input.

That is why insertion sort is fast on nearly sorted data: an array whose items are each at most a few places from their final positions has few inversions, so it costs about n comparisons plus one shift per inversion. Shuffled data has about n²/4 inversions on average, so all three sorts are quadratic there.

Exercise and self-check

Write the insertion sort that counts its shifts. The sample tests check, on 200 random lists, that the count equals the number of inversions.

Exercise · Easy · Python, JavaScript

Insertion sort that counts its shifts

Write insertion_sort(a) in shifts.py (JavaScript: insertionSort(a) in shifts.mjs). It sorts the list a in place, smallest first, with insertion sort, and returns the number of shifts: how many times an item moved one place to the right to make room for the item being inserted.

  • Compare items only with > (or <), never with a key or a sort function of the language.
  • Equal items must keep their input order, so an item never moves past an item equal to it.
  • An empty list and a list of one item need no shifts.

The sample tests check small lists, check that equal items keep their order, and then check on 200 random lists that your count equals the number of out-of-order pairs, which a slow double loop in the test counts.

Python · Starter code · shifts.py

def insertion_sort(a):
    """Sorts a in place with insertion sort and returns the number of shifts it made."""
    shifts = 0
    # Write the insertion sort here.
    return shifts
The sample tests · test_shifts.py
import random

from shifts import insertion_sort


class Card:
    """A card compared by its number only; the letter shows where it started."""

    def __init__(self, number, letter):
        self.number, self.letter = number, letter

    def __gt__(self, other):
        return self.number > other.number

    def __lt__(self, other):
        return self.number < other.number


def test_small_lists():
    """sorts small lists in place and returns their shift counts"""
    for values, shifts in [([5, 2, 8, 1, 9, 3, 7, 4], 13), ([], 0), ([42], 0), ([1, 2, 3], 0), ([3, 2, 1], 3)]:
        a = list(values)
        assert insertion_sort(a) == shifts, values
        assert a == sorted(values), values


def test_equal_items_keep_their_order():
    """equal items keep their input order"""
    cards = [Card(3, "a"), Card(1, "b"), Card(3, "c"), Card(2, "d"), Card(1, "e")]
    insertion_sort(cards)
    assert [c.letter for c in cards] == ["b", "e", "d", "a", "c"]


def test_shifts_equal_inversions():
    """on 200 random lists, the shift count equals the number of out-of-order pairs"""
    rng = random.Random(4)
    for _ in range(200):
        values = [rng.randint(0, 9) for _ in range(rng.randint(0, 30))]
        expected = sum(1 for i in range(len(values)) for j in range(i + 1, len(values)) if values[i] > values[j])
        a = list(values)
        assert insertion_sort(a) == expected, values
        assert a == sorted(values), values

JavaScript · Starter code · shifts.mjs

/** Sorts a in place with insertion sort and returns the number of shifts it made. */
export function insertionSort(a) {
  let shifts = 0;
  // Write the insertion sort here.
  return shifts;
}
The sample tests · shifts.test.mjs
import { test, assert } from 'mysmartcopilot:test';
import { insertionSort } from './shifts.mjs';

/** A card compared by its number only (through valueOf); the letter shows where it started. */
class Card {
  constructor(number, letter) {
    this.number = number;
    this.letter = letter;
  }
  valueOf() {
    return this.number;
  }
}

const sortedCopy = (values) => values.slice().sort((x, y) => x - y);

test('sorts small lists in place and returns their shift counts', () => {
  for (const [values, shifts] of [[[5, 2, 8, 1, 9, 3, 7, 4], 13], [[], 0], [[42], 0], [[1, 2, 3], 0], [[3, 2, 1], 3]]) {
    const a = values.slice();
    assert.equal(insertionSort(a), shifts, JSON.stringify(values));
    assert.equal(a.join(','), sortedCopy(values).join(','), JSON.stringify(values));
  }
});

test('equal items keep their input order', () => {
  const cards = [new Card(3, 'a'), new Card(1, 'b'), new Card(3, 'c'), new Card(2, 'd'), new Card(1, 'e')];
  insertionSort(cards);
  assert.equal(cards.map((c) => c.letter).join(''), 'bedac');
});

test('on 200 random lists, the shift count equals the number of out-of-order pairs', () => {
  let seed = 4;
  const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
  for (let k = 0; k < 200; k++) {
    const values = Array.from({ length: Math.floor(random() * 31) }, () => Math.floor(random() * 10));
    let expected = 0;
    for (let i = 0; i < values.length; i++) for (let j = i + 1; j < values.length; j++) if (values[i] > values[j]) expected++;
    const a = values.slice();
    assert.equal(insertionSort(a), expected, JSON.stringify(values));
    assert.equal(a.join(','), sortedCopy(values).join(','), JSON.stringify(values));
  }
});
A hint

Take item = a[i] out of the list and walk a position j down from i while a[j - 1] > item, copying a[j - 1] to a[j] each time: every copy is one shift. When the loop stops, put item at a[j]. Use > and not >=, or an item would move past an equal one.

The sample tests run on this device, in your browser (Pyodide, QuickJS): nothing is sent to mysmartcopilot.com. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Paste three_sorts.py and give it a sorted list and a reversed list: compare the counts of the three sorts.

Check yourself

4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 4 How many comparisons does selection sort make on 8 distinct items, whatever their starting order?

    Type a number.

    Show the answer to question 1

    Answer: 28 comparisons

    Pass i compares the remaining 8 − i items with the smallest found so far: 7 + 6 + 5 + 4 + 3 + 2 + 1 = 28, which is n(n − 1)/2 for n = 8. The traced run printed the same number.

  2. Question 2 of 4 After 3 passes of bubble sort on [5, 2, 8, 1, 9, 3, 7, 4], which part of the array is certain to be in its final place?

    Choose one answer.

    Show the answer to question 2

    Answer: The last 3 items, 7, 8 and 9

    Each pass carries the largest item it has seen to the end, so after 3 passes the 3 largest items sit at the end in order. The front happened to be 1, 2, 3, 5, 4 in the trace, but bubble sort promises nothing about it yet.

  3. Question 3 of 4 How many shifts does insertion sort make on [3, 1, 2]?

    Type a number.

    Show the answer to question 3

    Answer: 2 shifts

    Inserting 1 shifts 3 right (one shift); inserting 2 shifts 3 right again (another). The two shifts match the two out-of-order pairs, (3, 1) and (3, 2).

  4. Question 4 of 4 Which of the three sorts, written as in this lesson, keep equal items in their input order?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • Insertion sort
    • Bubble sort

    Bubble sort swaps only neighbours that are strictly out of order, and insertion sort shifts only strictly larger items, so equal items never pass each other. Selection sort's long-distance swap can throw an item past its equals.

Why each sort is correct, and its exact counts Pro: opens with a Pro, Premium or Ultimate pass

Every number in the table above follows from the three invariants. This part proves each invariant, checks the counts on every possible order of six items and works through counting questions of the kind that exam papers set.

In this part:

  • Each invariant, and why it proves the sort
  • Exact counts on every order of six items
  • Counting questions in the GATE style, worked

1 runnable example (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Stability and nearly sorted data, reproduced and measured Pro: opens with a Pro, Premium or Ultimate pass

The stable and adaptive columns of the table are claims a test can check. This part breaks selection sort’s stability on four records and repairs it, breaks insertion sort’s stability with one character, and times insertion sort on nearly sorted and shuffled input.

In this part:

  • Selection sort’s swap breaks stability, reproduced
  • One character breaks insertion sort’s stability
  • Nearly sorted against shuffled input, timed
  • Why libraries still use insertion sort for short runs

3 runnable examples (Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Insertion sort in five languages, with variants and practice Pro: opens with a Pro, Premium or Ultimate pass

Insertion sort is short enough to write from memory in any interview language, and the loop has the same traps in each. This part gives it in Python, JavaScript, Java, C++ and Go with recorded runs, then the variants of bubble sort worth knowing and a practice set with answers.

In this part:

  • Insertion sort in Python, JavaScript, Java, C++ and Go
  • Variants worth knowing, with answers
  • Practice set

2 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · about 3 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher · Warm-up

    Which of selection, bubble and insertion sort are stable, and what makes selection sort unstable?

    Bubble sort and insertion sort are stable as usually written: bubble sort swaps two neighbours only when they are strictly out of order, and insertion sort shifts only items strictly larger than the one it inserts, so equal items never pass each other. Selection sort is not stable in its array form. Each pass swaps the smallest remaining item with the item at the front of the unsorted part, and that front item can be thrown far to the right, past items equal to it. Sorting (b, 2), (a, 2), (c, 1) by the number swaps (c, 1) with (b, 2) in the first pass, and (b, 2) ends up behind (a, 2), although it came first.

  2. Fresher to mid

    A device sends 50,000 sensor readings almost in time order: network delays move each reading at most 3 places from where it belongs. You must sort them on a microcontroller with no spare memory. Which elementary sort do you use, and how much work does it do?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    Selection sort is not stable. Change it so that it is, and say what the change costs on an array of n items and on a linked list.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    You must sort 1,000 records of 4 KB each in flash memory, where writing a record costs 100 times as much as comparing two keys. Compare selection sort and insertion sort on shuffled records in that cost model, and choose.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Selection sort always makes n(n − 1)/2 comparisons and at most n − 1 swaps, and it is not stable.
  • Bubble sort with an early exit and insertion sort are stable and adaptive: n − 1 comparisons on sorted input.
  • Every bubble-sort swap and every insertion-sort shift fixes exactly one inversion, so both do one move per inversion.
  • Insertion sort costs about n plus the number of inversions, which is why it suits nearly sorted data.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.