Essential Algorithms to Learn: Sorting Algorithms in Python

Bubble Sort, Merge Sort, and Quick Sort Explained with Code

Sorting is one of the most fundamental operations in computer science. Whether you’re arranging names alphabetically or optimizing search performance, sorting algorithms are essential.

In this article, we’ll dive into three classic sorting algorithms:

  • 🫧 Bubble Sort (Simple, but inefficient)
  • 🧩 Merge Sort (Divide & Conquer based)
  • ⚡ Quick Sort (Fast and practical)

Each comes with its own strengths, weaknesses, and Python implementation.

📚 1. Bubble Sort

🫧 The Simplest Sort – Good for Learning

Concept:
Repeatedly compare each pair of adjacent elements and swap them if they are in the wrong order. Continue until no swaps are needed.
Time Complexity:

  • Best: O(n)
  • Average/Worst: O(n²)

Python Code:

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        swapped = False
        for j in range(0, 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

print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))

🖨️ Output:

[11, 12, 22, 25, 34, 64, 90]

Best For: Education, small datasets
Not Recommended for performance-critical applications

🧠 2. Merge Sort

🧩 A Powerful Divide & Conquer Algorithm

Concept:
Split the array into halves, recursively sort them, and then merge the sorted halves.

Time Complexity:

  • Best/Worst/Average: O(n log n)

Python Code:

def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

print(merge_sort([38, 27, 43, 3, 9, 82, 10]))

🖨️ Output:

[3, 9, 10, 27, 38, 43, 82]

Best For: Stable sorting, predictable performance
Requires extra space (O(n))

⚡ 3. Quick Sort

The Fast and Practical Sort

Concept:
Choose a “pivot” and partition the array into two sub-arrays:

  • One with elements smaller than the pivot
  • One with elements greater than the pivot
    Recursively apply the same logic.

Time Complexity:

  • Best/Average: O(n log n)
  • Worst: O(n²) (rare, with bad pivot choice)

Python Code:

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[0]
    less = [x for x in arr[1:] if x <= pivot]
    greater = [x for x in arr[1:] if x > pivot]
    return quick_sort(less) + [pivot] + quick_sort(greater)

print(quick_sort([10, 7, 8, 9, 1, 5]))

🖨️ Output:

[1, 5, 7, 8, 9, 10]

Best For: In-place sorting, average-case performance
Unstable, worst-case performance can degrade

📊 Performance Comparison

AlgorithmTime Complexity (Average)Stable?In-Place?Extra Space
Bubble SortO(n²)✅ Yes✅ Yes❌ No
Merge SortO(n log n)✅ Yes❌ No✅ Yes (O(n))
Quick SortO(n log n)❌ No✅ Yes✅ No

🎯 Choosing the Right Sort

  • Use Bubble Sort only for learning.
  • Use Merge Sort when stability and predictable performance matter.
  • Use Quick Sort for practical use, especially when space is limited.

🧪 Challenge: Sort with Custom Comparator

Try writing a sort that:

  • Sorts words based on length instead of alphabetical order
words = ["banana", "apple", "kiwi", "strawberry"]
sorted_words = sorted(words, key=len)
print(sorted_words)

🖨️ Output:

['kiwi', 'apple', 'banana', 'strawberry']

🧠 Summary

You’ve now learned:

  • The core logic behind 3 famous sorting algorithms
  • When and why to use each
  • Python implementations to practice