If you have to write a Python program for bubble sort, say for a class or a coding interview, the idea is simple: walk through the list, swap any two neighbors that are out of order, and repeat until a pass makes no swaps.
The swap is one line. It’s the loop bounds that trip you up: one step too far raises an IndexError, and without an early exit you’ll keep looping over a sorted list.
Below, I’ll walk you through three versions, from the plain nested loop to one that sorts records by any field, and why you’d skip it in real code. Outputs are from Python 3.12.5.
The sample prices we’ll sort with bubble sort
You’ll sort the same six order totals from a small online shop in every example:
prices = [24.99, 8.50, 129.00, 3.25, 45.75, 8.50] # order totals in USD
- $129.00, the largest, starts in the middle
- $8.50 shows up twice (two different orders)
The duplicate matters later, when you sort whole orders and want ties to keep their order. To see how bubble sort compares with merge sort and quicksort, read sorting algorithms in Python.
Method 1: Bubble sort in Python with two nested loops
This is the textbook version: the outer loop counts passes, the inner loop compares neighbors and swaps them, as in swapping two numbers in Python. Watch the prices move:
prices = [24.99, 8.50, 129.00, 3.25, 45.75, 8.50]
def bubble_sort(items):
n = len(items)
for i in range(n - 1):
for j in range(n - 1 - i): # last i items are in place
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
print(f"Pass {i + 1}: {items}")
return items
bubble_sort(prices)
Output:
Pass 1: [8.5, 24.99, 3.25, 45.75, 8.5, 129.0]
Pass 2: [8.5, 3.25, 24.99, 8.5, 45.75, 129.0]
Pass 3: [3.25, 8.5, 8.5, 24.99, 45.75, 129.0]
Pass 4: [3.25, 8.5, 8.5, 24.99, 45.75, 129.0]
Pass 5: [3.25, 8.5, 8.5, 24.99, 45.75, 129.0]

How does this code work?
In pass 1, j goes from 0 to 4. Whenever items[j] is bigger than the next price, they swap, so $129.00 bubbles to the end.
Each pass fixes one more item at the right, so the inner range shrinks by i. New to computed stops? See for i in range in Python.
So here’s the catch: the list is sorted after pass 3, but passes 4 and 5 still run. Method 2 fixes that.
Method 2: Optimized bubble sort with an early exit (Recommended)
The fix is a swapped flag: if a full pass makes no swaps, the list is sorted, so stop. Here’s the code on the same prices plus an already sorted list:
prices = [24.99, 8.50, 129.00, 3.25, 45.75, 8.50]
def bubble_sort(items):
n = len(items)
checks = 0
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
checks += 1
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if not swapped: # clean pass: done
break
print(f"passes: {i + 1}, comparisons: {checks} -> {items}")
return items
bubble_sort(prices)
bubble_sort([3.25, 8.50, 8.50, 24.99, 45.75, 129.00]) # already sorted
Output:
passes: 4, comparisons: 14 -> [3.25, 8.5, 8.5, 24.99, 45.75, 129.0]
passes: 1, comparisons: 5 -> [3.25, 8.5, 8.5, 24.99, 45.75, 129.0]

How does this code work?
swapped resets to False at the start of every pass, and any swap sets it to True. Pass 4 on our prices finds nothing to swap, so break stops after 14 comparisons. Method 1 runs 15 for both lists.
That makes the best case linear time. For other hand-written approaches, see sorting a list without the sort() function.
Method 3: Bubble sort in descending order or by a key
Real data is rarely a bare list of numbers. This version takes key and reverse arguments, like built-in sorted().
Here are four of our orders as (id, total) tuples, sorted highest total first:
def bubble_sort(items, key=lambda x: x, reverse=False):
items = list(items) # work on a copy
n = len(items)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
a, b = key(items[j]), key(items[j + 1])
if (a < b) if reverse else (a > b):
items[j], items[j + 1] = items[j + 1], items[j]
swapped = True
if not swapped:
break
return items
orders = [("A-1001", 24.99), ("A-1002", 8.50), ("A-1003", 129.00), ("A-1004", 8.50)]
for order in bubble_sort(orders, key=lambda o: o[1], reverse=True):
print(order)
Output:
('A-1003', 129.0)
('A-1001', 24.99)
('A-1002', 8.5)
('A-1004', 8.5)

How does this code work?
key(items[j]) pulls out the value to compare, here the total at index 1. With reverse=True, the test flips to a < b, so bigger values move left.
Look at the two $8.50 orders: A-1002 stays ahead of A-1004. Bubble sort only swaps on a strict difference, so ties never trade places. That makes it stable, like sorting a list of lists in Python with sorted().
Pro Tip: Use >, not >=, in the comparison. With >=, equal items swap on every pass and the sort is no longer stable.
Why is bubble sort so slow on large lists?
Bubble sort compares about n²/2 pairs in the worst case, so it’s O(n²): double the list and it gets roughly four times slower.
Let me show you with random prices (see generating random numbers in Python):
import random, timeit
def bubble_sort(items):
for i in range(len(items) - 1):
for j in range(len(items) - 1 - i):
if items[j] > items[j + 1]:
items[j], items[j + 1] = items[j + 1], items[j]
random.seed(2026)
for size in (1_000, 2_000, 4_000):
data = [round(random.uniform(1, 500), 2) for _ in range(size)]
slow = timeit.timeit(lambda: bubble_sort(data.copy()), number=1)
fast = timeit.timeit(lambda: sorted(data), number=1)
print(f"{size:>5} prices: bubble {slow * 1000:7.1f} ms, sorted() {fast * 1000:5.2f} ms")
Output:
1000 prices: bubble 28.8 ms, sorted() 0.08 ms
2000 prices: bubble 127.3 ms, sorted() 0.16 ms
4000 prices: bubble 537.1 ms, sorted() 0.38 ms

How does this code work?
Each size sorts a fresh copy. Built-in sorted() uses Timsort, which is O(n log n) and written in C. More on that in sorting an array in Python.
Picking the right bubble sort version
| Version | Best case | Use it when |
|---|---|---|
| Method 1: nested loops | O(n²) | You’re learning how passes work |
| Method 2: early exit | O(n) | Any assignment or interview answer |
| Method 3: key and reverse | O(n) | Sorting records or high to low |
sorted() / list.sort() | O(n) | Real code, always |
Next simple algorithm to try: selection sort in Python, which makes fewer swaps but is still O(n²).
Things to Keep in Mind With Bubble Sort in Python
- The inner loop must stop at
n - 1 - i. Going tonmakesitems[j + 1]raise an IndexError. - Methods 1 and 2 sort in place. Pass
prices.copy()to keep the original.
Once bubble sort clicks, these related guides are a natural next step:
- Binary search in Python
- Remove duplicates from a list in Python
- Sort a list alphabetically in Python
- Find the size of a list in Python
Frequently asked questions
How do you write a bubble sort program in Python?
Use two loops: the outer counts passes, the inner swaps out-of-order neighbors with a[j], a[j+1] = a[j+1], a[j].
What is the time complexity of bubble sort?
O(n²) on average and worst case, O(n) on a sorted list with an early-exit flag, and O(1) extra space.
How do I sort in descending order with bubble sort?
Flip the comparison from > to <, or add a reverse argument as in Method 3.
Is bubble sort stable?
Yes, if you swap only when one item is strictly greater.
Should I use bubble sort in real Python code?
No. Use sorted() or list.sort(); bubble sort is for learning and interviews.
Where is sorting in Python documented?
See the Sorting Techniques guide in the Python docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn