AlgorithmsBeginner

Bubble Sort in Python

Learn the Bubble Sort algorithm in Python! Step-by-step visual explanation with beginner-friendly, optimized Python code.

Try it yourself

Run this code directly in your browser. Click "Open in full editor" to experiment further.

Loading...

Click Run to see output

Or press Ctrl + Enter

How it works

Bubble Sort is the canonical "first sort" โ€” the algorithm everyone meets on day one. ๐Ÿ› It's a comparison sort (sorts by comparing pairs), an in-place sort (no extra array), and a stable sort (equal elements keep their relative order). It's also, famously, an O(n^2) sort โ€” exactly why it's a teaching tool, not a production tool.

The name comes from how large values appear to bubble to the end of the list, one position per pass. ๐Ÿซง Each pass walks left-to-right comparing neighbours and swaps when the left one is bigger. After pass 1 the biggest element is in place; after n - 1 passes the list is sorted.

How It Works

Let's trace it on a small array: [5, 3, 8, 1, 2] (n = 5).

Pass 1 (compare every adjacent pair from index 0 to n-2):

  • [5, 3, 8, 1, 2] โ†’ 5 > 3, swap โ†’ [3, 5, 8, 1, 2]
  • 5 < 8, leave
  • 8 > 1, swap โ†’ [3, 5, 1, 8, 2]
  • 8 > 2, swap โ†’ [3, 5, 1, 2, 8]
  • After pass 1, `8` has bubbled to the end โ€” we never touch that slot again.

    Pass 2 (scan only up to index n-3):

  • 3 < 5, leave
  • 5 > 1, swap โ†’ [3, 1, 5, 2, 8]
  • 5 > 2, swap โ†’ [3, 1, 2, 5, 8]
  • Now `5` is in place too.

    Pass 3: 3 > 1 โ†’ [1, 3, 2, 5, 8]; 3 > 2 โ†’ [1, 2, 3, 5, 8].

    Pass 4: 1 < 2, no swaps. Done! That "zero swaps in a whole pass" signal is the key to the optimised version below.

    The inner loop range shrinks each pass. After i passes, the last i slots are guaranteed correct โ€” that's why we use range(0, n - i - 1) and not range(0, n - 1). Forgetting that shrink is the #1 bubble sort bug.

    The Optimised Version

    The plain version always does n - 1 full passes, even on a list that's already sorted. The optimised bubble sort adds a boolean flag, swapped, set to True whenever a pass actually swaps. If a whole pass finishes with swapped still False, the list is sorted and we break out immediately.

    This cuts the best case from O(n^2) to O(n) โ€” one pass with zero swaps confirms sortedness and exits. For nearly-sorted data (a long list with one item at the wrong end), the optimised version finishes in a few passes instead of grinding through n - 1. Worst and average cases stay O(n^2) โ€” the flag can't save you from a fully-reversed list โ€” but for almost-sorted input it's a real win.

    Complexity Analysis

    VariantBestAverageWorstSpaceStableIn-place
    Plain bubble sortO(n^2)O(n^2)O(n^2)O(1)YesYes
    Optimised (early-exit)O(n)O(n^2)O(n^2)O(1)YesYes

    The O(1) space is auxiliary memory โ€” we sort the list itself, not a copy. The stability comes from the > comparison: we only swap when the left is strictly greater, so equal elements never change order.

    Bubble Sort vs Other Simple Sorts

    Bubble, selection, and insertion sort are all O(n^2) comparison sorts everyone learns first โ€” but they're not equal in practice.

    AlgorithmBestWorstStableNotes
    Bubble sortO(n)*O(n^2)Yes*Needs the optimised version
    Selection sortO(n^2)O(n^2)NoSame number of comparisons every time
    Insertion sortO(n)O(n^2)YesUsually the fastest of the three

    Insertion sort almost always beats bubble sort on real data โ€” it shifts elements instead of swapping pairs, so it does fewer writes. That's why CPython's Timsort uses insertion sort (not bubble sort) for small sub-arrays. Bubble sort's only real edge is conceptual simplicity.

    Common Pitfalls

  • Off-by-one on the inner loop. Writing range(n) instead of range(n - i - 1) triggers an IndexError when you read arr[j + 1] past the end.
  • Forgetting the `n - i - 1` shrink. Still correct, but you waste comparisons re-checking already-sorted slots โ€” every pass becomes full-length.
  • Swapping without tuple unpacking. arr[j] = arr[j+1] then arr[j+1] = arr[j] overwrites the value before you save it. Use arr[j], arr[j+1] = arr[j+1], arr[j] โ€” Python evaluates the right side first.
  • Calling it "efficient". It isn't. O(n^2) is the slowest tier of useful sorts. Interviewers wince.
  • Using it on big lists. A million items is roughly a trillion comparisons โ€” hours of work where Timsort takes milliseconds.
  • When (If Ever) to Use It

  • Pedagogy. The clearest way to introduce comparisons, swaps, passes, and loop invariants โ€” its real job.
  • Tiny lists (under ~10 items) where you need a sort and don't want to import anything (insertion sort is still better).
  • Nearly-sorted data with the optimised flag โ€” the early-exit makes it competitive.
  • Embedded systems where you need a sort in 5 lines of C and nothing else fits.
  • Why Production Code Doesn't Use It

    Python's built-in list.sort() and sorted() are powered by Timsort, a hybrid of merge sort and insertion sort designed by Tim Peters in 2002. Timsort is O(n log n) worst case, O(n) best case, stable, and adaptive โ€” it detects and exploits runs of already-sorted data. On real-world inputs it's dramatically faster than its worst-case bound suggests.

    Unless you're writing the algorithm for a class, a code-golf bit, or a deliberate teaching example, just use `sorted(my_list)` or `my_list.sort()`. You will not write a faster sort than Timsort. ๐Ÿ˜‰

    Frequently Asked Questions

    Is bubble sort stable?

    Yes. Because we only swap when arr[j] > arr[j + 1] (strictly greater), equal elements never swap, so they keep their original relative order. That makes bubble sort a stable sort โ€” useful when sorting records by one key while preserving a previous ordering on another.

    Why is it called bubble sort?

    Because on each pass the largest unsorted element rises all the way to the right โ€” like a bubble of air surfacing in water.

    Is bubble sort ever the best choice?

    Almost never in production. Insertion sort beats it on small arrays; Timsort beats it on everything else. Its niche is teaching.

    What's the difference between bubble sort and exchange sort?

    "Exchange sort" is a broader family โ€” any algorithm that repeatedly swaps out-of-order pairs. Bubble sort is the most famous member, but it only compares adjacent pairs.

    How does Python's built-in sort compare?

    It's not close. sorted() uses Timsort at O(n log n). On 10,000 random integers Timsort takes milliseconds; bubble sort takes seconds. On a million items the gap is hours vs milliseconds.

    Can I write bubble sort recursively?

    Yes โ€” each recursive call does one pass, then calls itself on arr[:n-1]. Fun exercise, zero performance benefit, and adds stack overhead, so the iterative version is what textbooks and interviews use.

    Related examples