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.
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
What you’ll learn · Before you start
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
On this page
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.
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 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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
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.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
-
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.
-
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?
-
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.
-
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.
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
- selection sort (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- bubble sort (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- insertion sort (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- bidirectional bubble sort (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- Shell sort (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology (NIST))
- GATE 2027 syllabus for Data Science and Artificial Intelligence (DA) (IIT Madras (GATE 2027 organising institute))
- GATE 2027 syllabus for Computer Science and Information Technology (CS) (IIT Madras (GATE 2027 organising institute))
- GATE 2027 question paper pattern (IIT Madras (GATE 2027 organising institute))
- Computer Science Curricula 2023: Algorithmic Foundations (AL) (ACM, IEEE Computer Society and AAAI)
- listsort.txt: the design of CPython's list sort (Python Software Foundation (CPython source))
- libc++ source: std::sort (include/__algorithm/sort.h) (The LLVM Project)
- time.perf_counter() (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress