3.6.1Sorting & Searching

Bubble sort, selection sort, insertion sort — O(n²), when insertion sort wins

2,151 words10 min readdifficulty · medium2 backlinks

WHY are these all O(n2)O(n^2)? (Derivation from scratch)

WHAT we count: the number of comparisons + the number of element moves, as a function of input size nn.

Selection sort — exact count (always the same)

HOW it runs: for each position ii from 00 to n2n-2, scan the rest to find the minimum.

  • Pass i=0i=0: compare against n1n-1 elements.
  • Pass i=1i=1: compare against n2n-2 elements.
  • … down to the last pass: 11 comparison.

Total comparisons: C=(n1)+(n2)++1=k=1n1k=(n1)n2C = (n-1) + (n-2) + \dots + 1 = \sum_{k=1}^{n-1} k = \frac{(n-1)n}{2}

Why this sum? The Gauss trick: pair the first and last term (n1)+1=n(n-1)+1=n, second and second-last (n2)+2=n(n-2)+2=n… there are n12\frac{n-1}{2} such pairs each summing to nn, giving n(n1)2\frac{n(n-1)}{2}.

Because the scan never stops early, C=n(n1)2=O(n2)C=\frac{n(n-1)}{2}=O(n^2) in best, worst, and average cases. Swaps, however, are at most n1n-1 (one per pass) — selection sort minimises writes.

Bubble sort — worst case

HOW: repeatedly pass through the array swapping adjacent inversions. After pass kk, the largest kk elements are locked at the end.

Worst case (reverse-sorted): pass kk does nkn-k comparisons: C=k=1n1(nk)=n(n1)2=O(n2)C = \sum_{k=1}^{n-1}(n-k) = \frac{n(n-1)}{2} = O(n^2)

With a "did I swap?" flag, best case (already sorted) = ==one pass, n1n-1 comparisons, O(n)O(n)== — but it does many swaps in the worst case (O(n2)O(n^2)).

Insertion sort — best vs worst

HOW: keep a sorted prefix A[0..i-1]. Take A[i], shift larger elements right, drop it in.

  • Best case (already sorted): each new element is \geq its left neighbour, so the inner loop stops immediately → 11 comparison per element → n1n-1 comparisons → ==O(n)O(n)==.
  • Worst case (reverse-sorted): element ii shifts past all ii predecessors: C=i=1n1i=n(n1)2=O(n2)C = \sum_{i=1}^{n-1} i = \frac{n(n-1)}{2} = O(n^2)

The number of moves equals the number of inversions in the array — this is the deep reason insertion sort is fast on nearly-sorted data.

Figure — Bubble sort, selection sort, insertion sort — O(n²), when insertion sort wins

The key insight: inversions


Worked examples



Recall Feynman: explain to a 12-year-old

Imagine sorting playing cards in your hand.

  • Insertion: you pick up cards one at a time and slide each into the right place among the cards you're already holding. If they were almost in order, you barely move anything — super quick!
  • Selection: you look at all the cards on the table, find the smallest, place it, then look at all the rest again, find the next smallest… You always look at everything, even if they were already sorted.
  • Bubble: you keep swapping any two neighbours that are in the wrong order, sweeping left to right, until nothing needs swapping. The big cards slowly "float" to the end. The trick: insertion sort is lazy in a good way — it stops the moment a card is already in place. That's why it wins when things are nearly tidy.

#flashcards/coding

Why is selection sort always Θ(n2)\Theta(n^2) even on a sorted array?
Its inner scan to find the minimum never stops early — it always makes n(n1)2\frac{n(n-1)}{2} comparisons regardless of input order.
What is the best-case time of insertion sort and when does it occur?
O(n)O(n), when the array is already (nearly) sorted — each element only checks its left neighbour and stops.
Insertion sort's number of moves equals what quantity?
The number of inversions in the array (out-of-order pairs).
Which two of the three sorts are naturally stable?
Bubble and insertion (they shift adjacent elements); selection is not stable by default.
Which sort minimises the number of writes/swaps, and why might that matter?
Selection sort — only n1n-1 swaps. Useful when writes are expensive (e.g. flash memory wear).
When does insertion sort beat bubble and selection in practice?
On small or nearly-sorted arrays — it's adaptive, so libraries use it for tiny sub-arrays inside faster sorts.
Derive selection sort's comparison count.
(n1)+(n2)++1=n(n1)2(n-1)+(n-2)+\dots+1 = \frac{n(n-1)}{2} by Gauss pairing.
What single optimisation makes bubble sort O(n)O(n) in the best case?
A "swapped?" flag: if a full pass makes no swaps, the array is sorted — stop.
Reverse-sorted input: how many comparisons does insertion sort do?
i=1n1i=n(n1)2\sum_{i=1}^{n-1} i = \frac{n(n-1)}{2}, its worst case O(n2)O(n^2).
Why is bubble sort rarely used despite the same Big-O as insertion?
It does far more swaps on average and no real advantage; insertion has better constants and is the standard small-array sort.

Connections

  • Merge SortO(nlogn)O(n\log n) divide-and-conquer; uses insertion sort for small chunks.
  • Quicksort / Introsort — switch to insertion sort below a size threshold.
  • Timsort — real-world hybrid built on insertion sort + merge, exploits existing runs.
  • Inversions and Counting — formal measure of "how unsorted" an array is.
  • Big-O Notation — why best/average/worst all matter beyond worst-case.
  • Stability in Sorting — preserving order of equal keys.
  • Lower Bound for Comparison Sorts — the Ω(nlogn)\Omega(n\log n) barrier these quadratic sorts don't beat.

Concept Map

includes

includes

includes

swaps adjacent pairs

moves equal

few when

makes fast

early stop gives

gives Bubble

uses

never stops early

only n-1 swaps

Comparison sort O n2

Bubble sort

Selection sort

Insertion sort

Number of inversions

Swap flag optimisation

Nearly-sorted data

Best case O n

Minimises writes n-1

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho yaar, teen sorting algorithms hain aur teeno ka worst case O(n2)O(n^2) hai — par inka "personality" alag hai. Selection sort har pass me poori unsorted list ko ghoorta hai minimum dhoondhne ke liye, chahe array pehle se sorted ho ya na ho — isliye comparisons hamesha n(n1)2\frac{n(n-1)}{2} hi rehte hain, koi shortcut nahi. Bubble sort bagal wale (adjacent) elements ko swap karta rehta hai, bade elements aage "float" karte hain; ek flag laga do toh best case me O(n)O(n) ho jaata hai. Insertion sort taash ke patte sort karne jaisa hai — naya element uthao aur apni sorted line me sahi jagah slide karke daal do, aur jaise hi chhota element mil jaaye, ruk jao.

Asli magic insertion sort me yahi "ruk jao" waali baat hai. Jab array almost sorted ho, toh har element bas apne left neighbour se compare hota hai aur turant settle ho jaata hai — total kaam sirf O(n)O(n). Technically, insertion sort jitne inversions (ulte pairs) honge utna hi kaam karta hai. Sorted array me 0 inversions → bijli ki speed. Yeh adaptiveness hi reason hai ki real libraries (Timsort, introsort) chhote chunks ke liye insertion sort use karti hain.

Ek common galti: "dono O(n2)O(n^2) hain toh equal fast honge" — galat! Big-O sirf worst case batata hai, average behaviour aur constants chhupa deta hai. Practice me insertion sort kam moves karta hai aur nearly-sorted data pe selection se kaafi tej hai. Doosri galti: "selection sort stable hai" — nahi, kyunki door se minimum ko swap karne se equal keys ka order bigad jaata hai. Insertion aur bubble stable hote hain kyunki woh shift karte hain, lambi jump nahi maarte.

Toh yaad rakho: chhote ya nearly-sorted data pe insertion sort jeetta hai kyunki woh aalsi hai par acche tareeke se — jaisे hi kaam khatam, ruk jaata hai. Selection sort bas tab kaam ka hai jab writes mehengi ho (kam swaps), warna insertion is the practical champion.

Go deeper — visual, from zero

Test yourself — Sorting & Searching

Connections