Exercises — Bubble sort, selection sort, insertion sort — O(n²), when insertion sort wins
Level 1 — Recognition
Goal: identify which algorithm is running just from what its work looks like.
Recall Solution L1.1
Answer: Bubble sort. Two clues, each decisive:
- It swapped only adjacent pairs (
5↔3, then8↔1). Selection swaps a possibly-distant minimum into the front; insertion shifts a run of elements. Only bubble is defined by neighbour-only swaps. - The largest element
8"floated" to the end in one pass — the signature of bubble sort, where each pass locks the current maximum at the back. (Selection would have put the minimum1at the front; insertion would have grown a sorted prefix on the left, giving[3,5,8,1].)
Recall Solution L1.2
Answer: Selection sort. It is the only one of the three whose comparison count is fixed regardless of input, because its inner scan to find the minimum never stops early: Flagged bubble and insertion both adapt — on a sorted array they'd make only comparisons (bubble does one clean swap-free sweep, then stops). A constant count that ignores order ⇒ selection sort.
Level 2 — Application
Goal: run each algorithm by hand, counting comparisons and moves.
Use the cost model above: count comparisons (read-questions) and moves/swaps (writes) in separate columns. The step figure below shows exactly how insertion sort opens a gap and slides cards — refer to it while tracing L2.1.

Recall Solution L2.1
Sorted prefix starts as [4]. (Follow the gap-and-slide panels in the figure: each grey arrow is one move.)
- Insert
2: compare2<4(1 comp) → shift4right (1 move) →[2,4,5,1]. - Insert
5: compare5<4? No (1 comp) → stop, no shift →[2,4,5,1]. - Insert
1: compare1<5yes → shift (comp 1, move 1);1<4yes → shift (comp 2, move 2);1<2yes → shift (comp 3, move 3); front reached → insert →[1,2,4,5]. Totals: comparisons ; moves . Check via inversions of[4,2,5,1]: pairs out of order are inversions moves. ✓
Recall Solution L2.2
- Pass 1: compare
2>1? yes → swap →[1,2,3](comp 1, swap 1). Compare2>3? no (comp 2). A swap happened this pass, so the flag says "keep going." - Pass 2: compare
1>2? no (comp 3). Compare2>3? no (comp 4). No swaps this pass → flag lets us stop. Totals: passes , comparisons , swaps (one swap writes). The second pass exists only to confirm the array is sorted — that's the price of the flag: one extra clean pass.
Recall Solution L2.3
- Pass : scan
[3,4,1,2], min is1(index 2) → swap indices 0,2 →[1,4,3,2]. (3 comparisons) - Pass : scan
[4,3,2], min is2(index 3) → swap →[1,2,3,4]. (2 comparisons) - Pass : scan
[3,4], min is3, already in place → no swap. (1 comparison) Totals: comparisons ✓, swaps (at most ).
Level 3 — Analysis
Goal: reason about costs across whole classes of inputs, not one array.
The figure below plots how the comparison count grows with input size for all three sorts. Read it as three stories: the magenta curve is the shared worst case (selection always sits here; bubble and insertion sit here only on reverse-sorted input); the orange line is the best case that flagged-bubble and insertion reach on already-sorted input; the violet double-arrow at measures "the winning gap" — how much work the adaptive sorts save when data is tidy. Everything in L3.1–L3.2 is just reading numbers off this picture.

Recall Solution L3.1
All three worst cases are the same sum — that is why they share one magenta curve in the figure:
(a) Selection always makes (best worst — the fixed staircase).
(b) Insertion makes only in the worst case: a strictly reverse-sorted array [6,5,4,3,2,1], where element must shift past all predecessors. This array has the maximum inversions — every pair is out of order.
(c) Flagged bubble also makes on reverse-sorted input: pass compares neighbours, and . On reverse-sorted data every sweep swaps something, so the flag never triggers early. The three counts agree — they differ only in best and average behaviour, not worst.
Recall Solution L3.2
Selection: comparisons. Insertion best case (already sorted): comparisons. So on already-sorted data of size 100, selection does 50× the comparison work of insertion. This gap widens with — it is , i.e. grows without bound. That single fact is why insertion sort wins on ordered data (it is the violet double-arrow in the figure, growing as grows).
Recall Solution L3.3
Inversion count. List pairs with : → inversions. So insertion sort will do exactly 3 moves (single-card shifts). Trace check:
- Insert
5:5>1stop, 0 moves. - Insert
2:2<5shift (1),2>1stop → 1 move. - Insert
6:6>5stop, 0 moves. - Insert
3:3<6shift (1),3<5shift (1),3>2stop → 2 moves. Total moves ✓ — matches the inversion count exactly.
Level 4 — Synthesis
Goal: combine ideas — choose or design an algorithm for a stated constraint.
Recall Solution L4.1
Choose selection sort. It performs at most swaps — one per pass — because each pass places exactly one element permanently. Every other element is read many times but written rarely. For : at most swaps. Using the cost model (a swap writes), that is at most writes. Contrast: insertion/bubble can perform up to moves on bad input — catastrophic for write-limited storage. Here the comparison count of selection () is irrelevant because reads are cheap.
Recall Solution L4.2
Insertion is cheaper when The key step — why we divide by : both sides contain a factor of , and for any array size that factor is strictly positive. Dividing an inequality by a positive quantity keeps the direction of "" unchanged (only dividing by a negative would flip it). So we may cancel one from each side safely: This is worth doing because it turns a quadratic-vs- comparison into a linear-vs-log one, which is far easier to test by hand. Now test integers:
- : ✓ (insertion cheaper).
- : ✓.
- : ✓.
- : ✗ (fast sort cheaper). (Here is multiplied through by 4 to give for the arithmetic.) Crossover: insertion wins for . This is exactly why real libraries like Timsort and Introsort set an insertion-sort cutoff in the tens of elements.
Level 5 — Mastery
Goal: prove a general statement and confront the deepest subtleties.
Recall Solution L5.1
(a) Insertion is stable. When inserting A[i], the inner loop shifts a predecessor right only while it is strictly greater than A[i] (the comparison is >), and stops as soon as it meets an element ≤ A[i]. So an earlier equal key is never shifted past A[i]; A[i] lands after all equal keys already placed. Original order among equals is preserved. ∎
(b) Selection is not stable. Take [2a, 2b, 1] (two keys equal to 2, subscripts mark original order).
- Pass 0: scan finds min
1at index 2, swaps it with index 0 →[1, 2b, 2a]. - Pass 1:
[2b, 2a]already in min-first order, no swap. Result:[1, 2b, 2a]— the two 2's are nowbbeforea, the reverse of their input order. Stability broken by a single long-distance swap. ∎
Recall Solution L5.2
Where the floor comes from (so we don't invoke it blindly). A comparison sort must be able to distinguish all possible input orderings. Each comparison has 2 outcomes (yes/no), so comparisons distinguish at most orderings. To separate all we need , i.e. By Stirling's approximation , so the information-theoretic floor is up to a lower-order term and constants. This is a floor (a minimum), not a ceiling. For , using the leading term as the benchmark:
- Selection: comparisons.
- Leading-term floor: comparisons. Ratio . Selection does roughly 51× the necessary comparison work at this size. The "waste" is the price of never reusing information across passes — each scan rediscovers order from scratch. Merge sort avoids this by remembering sorted sub-results.
Recall Solution L5.3
There are exactly unordered pairs of positions. Each pair is either in order or inverted — never both. So the inversion count can be at most the total number of pairs, .
This maximum is attained iff every pair is inverted, i.e. the array is strictly decreasing: [n, n-1, …, 2, 1].
Implication: for the standard one-by-one shifting implementation of insertion sort (the only version we use here, where each element moves one slot at a time), the number of moves equals the inversion count exactly — because each single shift removes precisely one inversion. So its absolute worst case is a reverse-sorted array, costing exactly moves, matching the bound from the parent note.
(Subtlety: a variant that finds the insertion point by binary search and then does one block move of the tail still performs element writes on reverse input, but its count of discrete "move operations" is bookkept differently. The clean "moves inversions" identity is specifically about the element-at-a-time shift.) ∎
Recall One-line summary of every level
Recognize by fingerprint (L1) → trace and count (L2, using the move/swap/comparison cost model) → reason over input classes via inversions (L3) → choose/design under constraints (L4) → prove stability, lower bounds, and extremes (L5).
← Back to the topic note · Related: Inversions and Counting · Stability in Sorting · Big-O Notation · Timsort · Introsort