Bubble Sort
Repeatedly swap adjacent out-of-order pairs so the largest remaining element bubbles to the end each pass.
Overview
Bubble sort makes repeated passes over the array, comparing each adjacent pair and swapping when the left is larger. After pass i, the i largest elements sit in their final positions at the end. It is stable, in-place, and with an early-exit flag adaptive — a sorted array costs one O(n) pass.
It is almost never used in production: Insertion Sort does the same O(n²) job with far fewer writes and better cache behaviour. Bubble sort survives as a teaching device for the concepts of passes, invariants, and stability, and as the number of swaps it performs equals the number of inversions in the input.
Intuition
A mental model before the formal terms.
Picture bubbles in a glass of water: on each pass the biggest bubble rises all the way to the surface, because whenever it meets a smaller neighbour they trade places. After each pass one more bubble has settled at the top, and the region still to be sorted shrinks by one.
How it works
- For pass
i = 0 … n - 2, walkjfrom0ton - 2 - i. - If
a[j] > a[j + 1], swap them. - Track whether any swap happened in the pass; if none did, the array is sorted — stop early.
- Each pass places the maximum of the unsorted prefix at index
n - 1 - i.
Why it works
Invariant after pass i: the last i elements are the i largest, in sorted order. Within a pass, the running maximum is carried right by successive swaps, so it ends at the boundary of the unsorted prefix.
A pass with no swaps means every adjacent pair is ordered, which for a total order implies the whole array is sorted — so early exit is safe.
Each swap removes exactly one inversion, so the swap count equals the inversion count and the algorithm terminates after at most n(n-1)/2 swaps.
Recognition
How to tell a problem wants this.
- The question is explicitly about bubble sort, stability, or counting adjacent swaps / inversions.
- "Minimum adjacent swaps to sort" — the answer is the number of inversions, which bubble sort performs exactly.
- Tiny inputs in a language with no built-in sort where clarity beats speed.
Interactive visualization
Play, step, change the input. ← → and space work too.
1for i in 0 .. n-1:2 swapped = false3 for j in 0 .. n-i-2:4 if a[j] > a[j+1]:5 swap(a[j], a[j+1])6 swapped = true7 if not swapped: breakPseudocode
1for i from 0 to n - 2:2 swapped = false3 for j from 0 to n - 2 - i:4 if a[j] > a[j + 1]:5 swap(a[j], a[j + 1]); swapped = true6 if not swapped: breakImplementations
1def bubble_sort(a: list[int]) -> None:21 · Setup3 n = len(a)42 · Outer pass loop5 for i in range(n - 1):6 swapped = False73 · Compare and swap adjacent pairs8 for j in range(n - 1 - i):9 if a[j] > a[j + 1]: # strict '>' keeps equal keys in order (stable)10 a[j], a[j + 1] = a[j + 1], a[j]11 swapped = True124 · Early exit when no swaps happened13 if not swapped:14 breakrange(n - 1 - i)yields the indices of the still-unsorted prefix; it is empty onn <= 1, so no special case is needed.- Tuple swap
a[j], a[j + 1] = a[j + 1], a[j]builds and unpacks a tuple; CPython optimises the 2-element case into rotating the stack. - Strict
>preserves stability. breakon a swap-free pass gives O(n) on sorted input.
Pure-Python loops are ~50-100x slower than list.sort() (C TimSort); use this only to learn.
list.sort()sorts in place and returnsNone;sorted(a)returns a new list. Both are stable TimSort.- Use
key=rather thancmp_to_keyfor custom orders; keys are computed once per element. - Type hint
list[int]needs Python 3.9+; useList[int]fromtypingon older versions.
- Writing
a = a.sort()— assignsNone. - Using
>=, which breaks stability. - Iterating
for j in range(n - 1)on every pass instead of shrinking byi.
- JS/TS
[10, 2, 5].sort()yields[10, 2, 5]because the default comparator converts to strings; pass(x, y) => x - y. C++std::sortand Pythonsortcompare numerically by default. - Stability of library sorts:
std::sortis unstable (introsort),std::stable_sortis stable;Array.prototype.sortis stable since ES2019; Pythonlist.sort/sortedare always stable (TimSort). - In-place mutation: C++ needs a non-const reference, JS/TS/Python pass arrays/lists by reference so the caller sees the changes.
- Swap idiom:
std::swapin C++, destructuring in JS/TS (may allocate), tuple unpacking in Python (optimised by CPython).
Complexity
Best case requires the early-exit flag. Swaps = number of inversions. Stable, in-place, adaptive.
Compare growth rates in the Complexity Explorer →When to use — and when not to
- Teaching passes, invariants, and stability.
- Detecting whether an array is already sorted, or nearly sorted, with one cheap pass.
- Counting adjacent swaps needed to sort (equals inversions) on small inputs.
- Any real workload — Insertion Sort is strictly better among simple
O(n²)sorts. - Inputs beyond a few hundred elements; use Merge Sort, Quick Sort, or the library sort.
Alternatives
Common mistakes
- Iterating the inner loop to
n - 1on every pass instead of shrinking byi, doubling the work. - Omitting the
swappedflag and losing theO(n)best case. - Using
>=in the comparison, which swaps equal elements and breaks stability.
Interview patterns
- Explain stability with a concrete example of two equal keys and show why
>(not>=) preserves order. - Count inversions: bubble sort in
O(n²), then improve toO(n log n)with a modified merge sort. - Cocktail shaker sort variant: alternate directions to fix "turtles" (small elements at the end).
- Recognizing the approach from an array and a targetIntermediate
- When space complexity mattersIntermediate
- Two pointers or hash map?Intermediate
- Convincing me your algorithm is correctExpert
- Two SumBeginner