Bubble Sort: Complete Explanation with Python Code
Bubble Sort is a simple sorting algorithm that repeatedly compares adjacent elements and swaps them if they are in the wrong order.
It is called Bubble Sort because larger elements gradually move toward the end of the array, just as bubbles rise to the surface.
Let's understand it from the ground up: the intuition, mathematical foundation, algorithm, Python implementation, dry run, correctness, and time complexity.
1. What Is Bubble Sort?
Suppose we have an unsorted array:
Our goal is to sort it in ascending order:
Bubble Sort works by comparing adjacent elements.
For every pair of adjacent elements:
- If the left element is smaller than or equal to the right element, do nothing.
- If the left element is greater than the right element, swap them.
The comparison is:
Then swap:
After one complete pass through the array, the largest element reaches the last position.
2. Understanding Bubble Sort Through an Example
Consider:
We will sort this array in ascending order.
Pass 1
We compare adjacent elements from left to right.
Comparison 1: 5 and 3
Since:
Swap them.
Comparison 2: 5 and 8
Since:
No swap is required.
Comparison 3: 8 and 4
Since:
Swap them.
Comparison 4: 8 and 2
Since:
Swap them.
After Pass 1:
The largest element, 8, is now in its correct position.
Pass 2
We do not need to compare the last element again because 8 is already in its final position.
Start with:
Comparison 1: 3 and 5
No swap.
Comparison 2: 5 and 4
Swap.
Comparison 3: 5 and 2
Swap.
After Pass 2:
Pass 3
Start with:
Comparison 1: 3 and 4
No swap.
Comparison 2: 4 and 2
Swap.
After Pass 3:
Pass 4
Start with:
Compare 3 and 2.
Swap:
The array is now sorted.
3. Python Implementation
Let's implement the standard Bubble Sort algorithm.
3.1 Basic Implementation
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
Example
Output:
This implementation sorts the array in place, meaning it modifies the original list instead of creating a separate sorted list.
4. Understanding the Code Line by Line
Let's examine the implementation carefully.
Defines a function that accepts an array.
Stores the number of elements in the array.
For example:
Outer Loop
The outer loop controls the number of passes.
For an array of length \(n\), at most \(n-1\) passes are needed.
Why?
After every pass, the largest remaining unsorted element reaches its correct position.
After \(n-1\) passes, all elements must be sorted.
Inner Loop
The inner loop performs adjacent comparisons.
The expression:
determines how far the current pass needs to go.
Why subtract \(i\)?
Because after each pass, one additional element is fixed at the end.
For example, if:
Then:
| Pass | i |
Inner-loop comparisons |
|---|---|---|
| 1 | 0 | 4 |
| 2 | 1 | 3 |
| 3 | 2 | 2 |
| 4 | 3 | 1 |
The number of comparisons decreases after every pass.
Comparison
This checks whether two adjacent elements are in the wrong order.
For ascending order:
is the desired relationship.
If the relationship is violated, we swap the elements.
Swap
Python allows simultaneous assignment.
This swaps the two elements without requiring a temporary variable.
For example:
After the swap:
5. Mathematical Foundation of Bubble Sort
Now let's understand why the algorithm works.
5.1 Key Property
After the \(i\)-th complete pass, the last \(i\) elements are in their final sorted positions.
For example:
Initial:
[5, 3, 8, 4, 2]
After Pass 1:
[3, 5, 4, 2, 8]
After Pass 2:
[3, 4, 2, 5, 8]
After Pass 3:
[3, 2, 4, 5, 8]
After three passes, the last three elements are sorted:
The remaining unsorted portion is:
Only one more pass is required.
5.2 Loop Invariant
A loop invariant is a property that remains true at a particular point in an algorithm.
For Bubble Sort, we can state:
At the beginning of pass \(i\), the last \(i\) elements contain the \(i\) largest elements in sorted order.
Initialization
Before the first pass:
There are no elements in the sorted suffix.
The invariant holds trivially.
Maintenance
During each pass, the largest element in the unsorted prefix moves toward the right.
After the pass, that element occupies its correct final position.
Therefore, the sorted suffix grows by one element.
Termination
After \(n-1\) passes, the sorted suffix contains \(n-1\) elements.
The remaining element must also be in its correct position.
Hence, the array is sorted.
6. Time Complexity: Detailed Mathematical Analysis
This is one of the most important parts of understanding Bubble Sort.
The standard implementation contains two nested loops.
Let's derive its complexity mathematically.
6.1 Number of Comparisons
For an array of length \(n\):
- First pass: \(n-1\) comparisons.
- Second pass: \(n-2\) comparisons.
- Third pass: \(n-3\) comparisons.
- Final pass: \(1\) comparison.
Therefore, the total number of comparisons is:
Using the arithmetic series formula:
we get:
Expanding:
The dominant term is:
Therefore:
The standard implementation has:
- Best-case time: \(O(n^2)\)
- Average-case time: \(O(n^2)\)
- Worst-case time: \(O(n^2)\)
Even if the array is already sorted, the standard implementation still performs all the comparisons.
7. Optimized Bubble Sort
We can improve the best-case performance by detecting whether any swaps occurred during a pass.
If no swaps occur, the array is already sorted.
There is no need to continue.
Python Code
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:
break
return arr
What changed?
We introduced:
At the beginning of each pass.
Whenever a swap occurs:
At the end of the pass:
If no swaps occurred, the array is sorted.
8. Complexity of Optimized Bubble Sort
The optimized version has different best-case and worst-case complexities.
| Case | Time complexity | Explanation |
|---|---|---|
| Best case | \(O(n)\) | Already sorted; one pass |
| Average case | \(O(n^2)\) | Many comparisons and swaps |
| Worst case | \(O(n^2)\) | Reverse-sorted array |
| Auxiliary space | \(O(1)\) | In-place sorting |
Best-case derivation
For an already sorted array:
The algorithm performs:
comparisons in the first pass.
No swaps occur.
The algorithm stops.
Therefore:
Hence:
Worst-case derivation
For a reverse-sorted array:
The algorithm performs:
comparisons.
Therefore:
9. Stability and In-Place Sorting
Bubble Sort has two important properties.
Stability
A sorting algorithm is stable if equal elements retain their original relative order.
Bubble Sort is stable when it swaps elements only when:
It does not swap equal elements.
For example, suppose we have:
After sorting by the first value:
The two elements with value 5 retain their original relative order.
In-Place Sorting
Bubble Sort modifies the original array.
It uses only a constant number of additional variables.
Therefore:
This is its auxiliary space complexity.
10. Bubble Sort vs. Other Sorting Algorithms
| Algorithm | Best case | Average case | Worst case |
|---|---|---|---|
| Bubble Sort (optimized) | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) |
| Selection Sort | \(O(n^2)\) | \(O(n^2)\) | \(O(n^2)\) |
| Insertion Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) |
| Merge Sort | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n\log n)\) |
| Heap Sort | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n\log n)\) |
| Quick Sort | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n^2)\) |
Bubble Sort is primarily useful for learning sorting fundamentals.
For large arrays, algorithms such as Merge Sort, Heap Sort, and Quick Sort generally offer better asymptotic performance.
It is generally not suitable for large datasets because of its quadratic time complexity.
For practical Python sorting, use:
or:
Python's built-in sorting uses Timsort, which is designed to perform efficiently on real-world data.