Bubble Sort
Adjacent elements swap their way to order — simple, visual, and famously slow.
Hands on Interactive visualization
Implementation Code
export function bubbleSort(a: number[]): number[] {
const n = a.length;
for (let i = 0; i < n - 1; i++) {
let swapped = false;
// After i passes, the last i slots hold their final values.
for (let j = 0; j < n - 1 - i; j++) {
if (a[j] > a[j + 1]) { // strict > keeps equal keys stable
[a[j], a[j + 1]] = [a[j + 1], a[j]];
swapped = true;
}
}
if (!swapped) break; // clean pass: already sorted (O(n) best case)
}
return a;
}Big-O Complexity
O(n) best case requires the early-exit flag; without it every input costs O(n²).
In production When to use
The canonical first sorting algorithm: every step is visible and the invariant (largest unsorted element bubbles to the end each pass) is easy to verify by eye.
On tiny microcontrollers sorting a handful of sensor readings, a five-line loop with zero allocations and zero stack growth can beat pulling in a library sort.
With the early-exit flag, a single O(n) pass doubles as an is-sorted check that also repairs one out-of-place element as it scans.
Historically used for per-frame depth sorting of almost-sorted polygon lists, where each frame needs only a pass or two — though insertion sort does the same job with fewer writes.
Its simplicity makes it a common reference implementation when property-testing a faster sort: compare outputs against bubble sort on small random inputs.
Trade-offs Strengths & weaknesses
- Trivial to implement correctly — hard to get wrong in an interview or on a whiteboard.
- Stable: equal keys keep their original order.
- In-place with O(1) extra memory and no recursion.
- Adaptive with the early-exit flag: O(n) on already-sorted input.
- Detects sortedness as a side effect of running.
- O(n²) comparisons and, worse, O(n²) writes on average — the most swap-heavy of the quadratic sorts.
- Consistently slower in practice than insertion sort, its direct quadratic competitor.
- Poor cache behavior relative to work done: every pass rescans the whole unsorted prefix.
- No serious production use case that insertion sort does not handle better.
- Performance collapses on reverse-sorted input: maximal swaps every pass.
Intuition Mental model
Bubbles rising in a glass
Picture array values as bubbles of different sizes in a vertical glass of water. Each pass, the largest remaining bubble rises past its smaller neighbors until it reaches the surface.
- Compare each bubble with the one directly above it (adjacent pair).
- If the lower bubble is bigger, they trade places — the big one rises one slot.
- By the end of one full pass, the biggest unsorted bubble has surfaced: it is in its final position.
- Repeat on the shrinking region below the surfaced bubbles.
- If a pass completes with no trades, everything has settled — stop early.
Prep Interview questions
How do you make bubble sort O(n) on already-sorted input?
Why is bubble sort stable?
Bubble sort vs insertion sort: same complexity class, so why does insertion sort win in practice?
What is the invariant after the k-th pass of bubble sort?
What is cocktail (bidirectional) bubble sort and what problem does it fix?
Watch out Common bugs
Using j < n - 1 instead of j < n - 1 - i re-compares the already-settled suffix. Still correct, but doubles the work and hides the algorithm's invariant.
Comparing a[j] with a[j+1] while letting j reach n - 1 reads one past the end. The inner bound must exclude the last index, not include it.
Declaring swapped outside the outer loop and never resetting it per pass means the flag reflects the whole history, not the current pass, so the early exit never triggers after the first swap.
Swapping on equality destroys stability and performs useless writes. Only swap when the left element is strictly greater.
One screen Cheat sheet
Repeatedly swap adjacent out-of-order pairs; the max floats to the end each pass.
- Stable, in-place, comparison-based.
- After pass k, the last k elements are final — shrink the inner bound.
- Early-exit flag gives O(n) best case on sorted input.
- Most write-heavy quadratic sort; insertion sort dominates it in practice.
- Strict > comparison is what preserves stability.