Bubble Sort

Repeatedly swap neighboring out-of-order elements until the whole list is sorted, simple but slow on large lists.

What is bubble sort?

Bubble sort walks the array comparing each pair of neighbors, swapping them whenever the left one is bigger than the right one. Do that walk enough times and the biggest values keep getting nudged one step at a time toward the end, the way the biggest bubbles in a fizzy drink rise to the top fastest.

Pass
One full walk through the array, comparing every adjacent pair once
Adjacent swap
Swapping two neighboring elements because the left one is bigger than the right one
Sorted tail
The end portion that's already in its final position and doesn't need checking again

Why bubble sort at all?

Picture bubbles rising through a glass of soda. The biggest ones push past their smaller neighbors fastest, because at every point where a big bubble sits just below a smaller one, they swap places.

Bubble sort works the same way: compare two neighbors, swap them if they're in the wrong order, and move on to the next pair. By the end of one pass, the largest unsorted value has "risen" all the way to its correct spot at the end.

One pass over five values
Comparing5 · 1Actionswap

Each swap nudges the bigger value one step closer to the end.

What does each pass actually do?

Every pass is the same four-part rhythm: compare a pair, swap if needed, keep walking, and remember what's already settled so you never recheck it.

Compare, then swap if needed

Look at arr[i] and arr[i+1]. If arr[i] is bigger, swap them. If not, leave them alone and move the comparison one step to the right.

Three values · one swap needed
Comparing3 · 1Actionswap

3 > 1, so they swap. 3 < 2 next, so they swap again.

A full pass bubbles the max to the end

Repeat the compare-and-swap step across the whole array, left to right. Whatever value keeps winning its comparisons keeps moving right — by the end of the pass, the largest value in the unsorted portion sits exactly where it belongs.

Five values · one full pass
Comparing4 · 2Actionswap

6 wins every comparison it's in, so it ends the pass at index 4.

The sorted tail only grows

Each pass locks in one more correct value at the end. The next pass never needs to look at that locked region again, so it only compares the shrinking unsorted prefix.

Four values · locking in after each pass
Pass1Lockedlast 1

The locked region (dimmed) never gets compared again.

No swaps means you're done

Track whether any swap happened during a pass. If a whole pass goes by with zero swaps, every element is already in order — stop immediately instead of running the remaining passes.

Five values · already sorted after pass one
Comparing1 · 2Actionkeep

Zero swaps this pass — the array was already sorted, so bubble sort stops early.

Two versions

The basic version always runs n - 1 passes, whether or not it needs to. The optimized version adds one flag and stops the moment a pass makes no swaps.

Basic pass

Two nested loops: the outer loop counts down the unsorted region, the inner loop walks it comparing and swapping neighbors. It never checks whether it finished early — it just runs every pass.

Four values · every pass runs, needed or not
Pass1Swaps2

Pass 3 runs even though pass 2 already finished the job.

bubble-sort-basic.tsTypeScript
function bubbleSort(arr: number[]): number[] {
  const n = arr.length;

  for (let pass = 0; pass < n - 1; pass++) {
    for (let i = 0; i < n - 1 - pass; i++) {
      if (arr[i] > arr[i + 1]) {
        [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // swap
      }
    }
    // no check here — every pass runs, even a wasted one
  }

  return arr;
}

Early-exit optimized

Same nested loops, plus one swapped flag reset to false at the start of every pass. If it's still false after the inner loop finishes, nothing moved — the array is sorted, so break out early.

Five values · stops as soon as a pass finds nothing to swap
Pass1Swaps2

Pass 2 finds zero swaps, so passes 3 and 4 never run.

bubble-sort-early-exit.tsTypeScript
function bubbleSort(arr: number[]): number[] {
  const n = arr.length;

  for (let pass = 0; pass < n - 1; pass++) {
    let swapped = false;

    for (let i = 0; i < n - 1 - pass; i++) {
      if (arr[i] > arr[i + 1]) {
        [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // swap
        swapped = true;
      }
    }

    if (!swapped) break; // nothing moved this pass — already sorted
  }

  return arr;
}

Where it works — and where it breaks

Bubble sort's whole trade is simplicity: it's easy to write correctly, uses no extra memory, and handles small or nearly-sorted input just fine. The cost is what happens as the input grows.

Works well on small or nearly-sorted input

When only a few pairs are out of order, each pass finds almost nothing to swap and the early-exit version stops after a couple of passes — close to O(n) in practice.

Nearly sorted · one swap total
Pass1Swaps1

Breaks down on large or reverse-sorted input

A reverse-sorted array forces a swap on every single comparison, every pass — O(n²) comparisons and swaps. Double the input and the work roughly quadruples.

Reverse sorted · a swap on every comparison
Pass1Swaps3