The Story That Explains Bubble Sort
Bubble Sort works the exact same way. In every pass through the list, the largest remaining value "bubbles up" to its correct position at the end, one neighbour-swap at a time. Do this enough times and every value floats to where it belongs. That rising-bubble image is literally where the algorithm gets its name.
Bubble Sort is the friendliest sorting algorithm to learn first. It only ever does one simple thing: look at two side-by-side items, and if the left one is bigger, swap them. Repeat across the whole list, again and again, until nothing needs swapping. No clever tricks, no recursion, no extra memory — just compare-and-swap.
If, on a full sweep through the list, you make zero swaps, the list must already be sorted — so you can stop early. That single observation turns the naive version into the optimised version you'll build below.
How It Works — One Pass at a Time
A "pass" means walking from the start of the list to the end, comparing each adjacent pair. Here is exactly what happens, step by step:
The largest value (9, in red) is the one that bubbles to the top fastest each pass.
Hands-On: Watch Each Line Run
This is the heart of the tutorial. Press Play (or Step) and watch two things move together: the bars on the left rearrange, while the exact Python line doing that work lights up on the right. The status box tells you, in plain English, what that line is doing right now.
def bubble_sort(arr):
n = len(arr)
for i in range(n):
swapped = False
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
swapped = True
if not swapped:
break
return arr
Amber bars = the pair currently being compared. Red bars = a swap is happening. Green bars = locked in their final sorted position. The amber-highlighted code line is the instruction the interpreter is executing at that instant.
The Code, Line by Line — "This Line Does That"
Same program as the animation, now annotated. Each line maps to exactly one job the visualiser highlights.
def bubble_sort(arr):
n = len(arr) # how many items to sort
for i in range(n): # outer loop -> one pass each time
swapped = False # did we change anything this pass?
for j in range(n - i - 1): # inner loop, skips the sorted tail
if arr[j] > arr[j + 1]: # left bigger than right?
arr[j], arr[j+1] = arr[j+1], arr[j] # swap the pair
swapped = True # remember we made a change
if not swapped: # no swaps in a whole pass?
break # already sorted - stop early!
return arr # the sorted list
def bubble_sort(arr) — defines the function and receives the list to sort.
n = len(arr) — stores the length so the loops know their bounds.
for i in range(n) — the outer loop. Each iteration is one full pass.
swapped = False — resets the "did anything move?" flag at the start of every pass.
for j in range(n - i - 1) — the inner loop. The - i skips the already-sorted tail, so each pass is shorter.
if arr[j] > arr[j+1] — the comparison. Are this pair out of order?
arr[j], arr[j+1] = ... — the swap, done in one clean Python tuple assignment (no temp variable).
swapped = True — records that this pass changed something.
if not swapped — the early-exit check after each pass.
break — if a whole pass made no swaps, the list is sorted, so leave immediately.
return arr — hand back the sorted list.
A Full Hand-Trace
Let's sort [5, 1, 4, 2, 8] by hand so you can see what the animation computes. Bold = the pair being compared; arrows show a swap.
| Pass | Comparison | List State | Action |
|---|---|---|---|
| 1 | 5 & 1 | 5 1 4 2 8 | Swap → 1 5 4 2 8 |
| 1 | 5 & 4 | 1 5 4 2 8 | Swap → 1 4 5 2 8 |
| 1 | 5 & 2 | 1 4 5 2 8 | Swap → 1 4 2 5 8 |
| 1 | 5 & 8 | 1 4 2 5 8 | No swap (8 locked) |
| 2 | 1 & 4 | 1 4 2 5 8 | No swap |
| 2 | 4 & 2 | 1 4 2 5 8 | Swap → 1 2 4 5 8 |
| 2 | 4 & 5 | 1 2 4 5 8 | No swap (5 locked) |
| 3 | full pass | 1 2 4 5 8 | 0 swaps → break early ✓ |
Pass 3 made zero swaps, so the optimised version stops without doing a wasted fourth pass. On an already-sorted list, this makes Bubble Sort finish in a single pass — its best case of O(n).
Naive vs Optimised
| Input | Passes done |
|---|---|
| Random | n |
| Already sorted | n (wasteful!) |
| Best case | O(n²) |
| Stops early? | Never |
| Input | Passes done |
|---|---|
| Random | ≤ n |
| Already sorted | 1 only ✓ |
| Best case | O(n) |
| Stops early? | Yes |
The Naive Version (for comparison)
def bubble_sort_naive(arr):
n = len(arr)
for i in range(n):
for j in range(n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr # no swapped flag -> never quits early
Time & Space Complexity
| Case | When | Comparisons | Complexity |
|---|---|---|---|
| Best | Already sorted | n - 1 | O(n) |
| Average | Random order | ~ n²/2 | O(n²) |
| Worst | Reverse sorted | n(n-1)/2 | O(n²) |
| Space | Any | in-place | O(1) |
| Stable? | Equal keys keep order | — | Yes ✓ |
You run up to n passes, and each pass compares up to n pairs. n × n = n². Doubling the list size roughly quadruples the work. Sorting 1,000 items takes ~1,000,000 comparisons — which is why Bubble Sort is a teaching tool, not a production sorter for large data.
Benefits
swapped flag, a nearly-sorted or fully-sorted list is confirmed in a
single pass and exits immediately.
When To Use It — and When Not To
sorted() / list.sort() use Timsort — far faster.Bubble Sort vs Other Simple Sorts
| Algorithm | Best | Average / Worst | Space | Stable | Notes |
|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(n²) | O(1) | Yes | Simplest; detects sorted input |
| Selection Sort | O(n²) | O(n²) | O(1) | No | Fewest swaps |
| Insertion Sort | O(n) | O(n²) | O(1) | Yes | Great on nearly-sorted data |
| Merge Sort | O(n log n) | O(n log n) | O(n) | Yes | Reliable, needs extra memory |
| Quick Sort | O(n log n) | O(n²) worst | O(log n) | No | Fast in practice |
Golden Rules
swapped flag. It costs one boolean and turns the
best case from O(n²) into O(n) by exiting early on sorted data.
n - i - 1, not n - 1. The - i
skips the already-sorted tail and is the reason later passes are shorter.
a, b = b, a — no temporary variable needed,
and it reads exactly like the intent.
> (strictly greater),
never on >=. Swapping equal items would scramble their original order.
sorted() or list.sort() — Python's Timsort is dramatically faster.