Algorithms
Bubble Sort
The slow sort that's still worth understanding
Bubble sort repeatedly walks through an array, comparing each adjacent pair and swapping them if they're out of order. After n-1 passes, the array is sorted. It runs in O(n²), which makes it useless for production but invaluable for teaching — every other sort is a smarter version of "compare and swap."
- Time (worst, average)O(n²)
- Time (best, optimized)O(n)
- SpaceO(1)
- StableYes
- In-placeYes
- AdaptiveYes (with early-exit optimization)
Interactive visualization
Press play, or step through manually. The visualization is yours to drive — try it before reading on.
Watch the 60-second explainer
A condensed visual walkthrough — narrated, captioned, under a minute.
How bubble sort works
Walk through the array. For each adjacent pair, if the left element is greater than the right, swap them. After one pass, the largest element is at the end. Repeat with the remaining unsorted prefix. Repeat until no swaps happen on a full pass.
Why "bubble"? Each pass causes the largest remaining element to "bubble up" to its final position at the end. The same metaphor explains why each subsequent pass can ignore the last position — it's already locked in.
Concrete example. Sort [5, 1, 4, 2, 8]:
Pass 1: [1, 4, 2, 5, 8] (5 bubbled to end)
Pass 2: [1, 2, 4, 5, 8] (4 bubbled to position 2)
Pass 3: [1, 2, 4, 5, 8] (no swaps — early exit)
The early-exit optimization
A naive bubble sort always runs n-1 passes regardless of the input. The fix is one extra boolean: track whether any swap happened on the current pass. If no swaps, the array is sorted — return.
// Naive — always O(n²)
function bubbleNaive(arr) {
for (let i = 0; i < arr.length - 1; i++)
for (let j = 0; j < arr.length - 1; j++)
if (arr[j] > arr[j+1]) [arr[j], arr[j+1]] = [arr[j+1], arr[j]];
return arr;
}
// Optimized — O(n) on sorted input, O(n²) worst case
function bubbleOptimized(arr) {
for (let i = 0; i < arr.length - 1; i++) {
let swapped = false;
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j+1]) {
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
swapped = true;
}
}
if (!swapped) return arr;
}
return arr;
}
Two improvements: the early-exit flag, and the inner loop's length - 1 - i bound (since the last i positions are already sorted from previous passes).
When to use bubble sort
- Teaching. The clearest expression of "compare adjacent, swap if out of order" — the conceptual core of comparison sorting.
- Tiny inputs you know are nearly sorted. With the early-exit optimization, an already-sorted check is O(n). A real sort would also be fast here, but if you're avoiding a library dependency, bubble sort is small.
- Embedded code with strict size constraints. Bubble sort's implementation is the smallest of any correct sort — useful when binary size matters more than runtime.
Use cases where bubble sort is wrong: anything user-facing, anything with n > ~50, anything you'd run on a server, anything where someone might benchmark you. Production code uses Timsort (Python, Java for objects), pdqsort (Rust), or quicksort variants.
Bubble sort vs other O(n²) sorts
| Bubble sort | Selection sort | Insertion sort | |
|---|---|---|---|
| Time (worst) | O(n²) | O(n²) | O(n²) |
| Time (best) | O(n) optimized | O(n²) | O(n) |
| Space | O(1) | O(1) | O(1) |
| Stable | Yes | No (basic) | Yes |
| Swaps (worst) | O(n²) | O(n) | O(n²) |
| Real-world speed | Slowest | Slow | Fastest of three |
| Used as cutoff in std libs | No | No | Yes (n < ~16) |
Insertion sort is the only one of the three that production sorting libraries use — as the cutoff for small subarrays inside a quicksort or merge-sort. Bubble sort never appears in production code.
Pseudo-code
function bubbleSort(arr):
n = arr.length
for i from 0 to n - 2:
swapped = false
for j from 0 to n - 2 - i:
if arr[j] > arr[j+1]:
swap arr[j] and arr[j+1]
swapped = true
if not swapped: return arr # already sorted
return arr
Python implementation
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
return arr
return arr
Python's builtin sorted() uses Timsort, which is dramatically faster — about 100× on a million-element list. Use the builtin in any real code.
Common bugs and edge cases
- Inner loop bound off by one. The inner loop should run to
n - 1 - i, notn. The lastielements are already sorted; bounding past them does redundant work and can also index out of bounds in some languages. - Stability lost with
<=. Swap onarr[j] > arr[j+1]is stable. Swap onarr[j] >= arr[j+1]swaps equal elements unnecessarily AND breaks stability. - Forgetting the early exit. Without the swapped flag, your "optimized" bubble sort runs all n-1 passes even on a sorted input. The optimization is one variable; always include it.
- Trying to make it competitive. No matter how you optimize bubble sort — cocktail shaker sort, comb sort, etc. — it never beats O(n log n) sorts on real inputs. Don't fight the asymptote.
Frequently asked questions
Why is bubble sort O(n²)?
Each pass through the array does up to n-1 comparisons. To fully sort an array of n elements, you need up to n-1 passes (in the worst case the smallest element starts at the end and "bubbles" one position per pass). n-1 passes × n-1 comparisons ≈ n² operations.
When is bubble sort actually fast?
On nearly-sorted input, with the early-exit optimization, bubble sort is O(n). It does a single pass, finds no swaps to make, and returns. Insertion sort beats it even here, but on tiny inputs (under ~10 elements) the constant factors are similar and the difference is irrelevant.
Why teach bubble sort if it's never used?
It's the simplest correct comparison sort. Once you understand bubble sort, you understand why merge sort and quick sort exist (to do better than O(n²)), why insertion sort exists (to be similar but faster in practice), and what "stable" and "in-place" actually mean. It's pedagogical scaffolding.
Is bubble sort the same as insertion sort?
No. Bubble sort moves the largest unsorted element to the end on each pass. Insertion sort takes the next unsorted element and slides it into the correct position in the sorted prefix. Both are O(n²) but insertion sort has lower constant factors and is what most "small array" cutoffs use in production sorting libraries.
What's the early-exit optimization?
After a pass that performs zero swaps, the array is sorted — bail out. Adds one boolean flag per pass; turns the best-case from O(n²) to O(n) on already-sorted input. Cheap and worth doing in any teaching implementation.
Is bubble sort stable?
Yes, when implemented correctly with strict less-than (`<`) on swap conditions. Equal elements never swap, so their relative order is preserved. Switching to `<=` breaks stability.