Skip to Content
Course content

445: Implementing Common Sorting Algorithms in Python

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Look, I'll be the first to admit it: in a production environment, you should almost never write your own sorting algorithm. Python's list.sort() and sorted() use Timsort, which is a highly optimized hybrid that will outperform almost anything you can whip up in a few minutes. But if you're walking into a technical interview or trying to optimize a very specific data structure, you need to understand the mechanics of how we get from a chaotic list to an ordered one.

The trap of the nested loop

When people first try to implement a sort, they usually gravitate toward something like Bubble Sort. It feels intuitive because it mimics how you might physically organize a hand of cards: you look at two items, swap them if they're in the wrong order, and repeat. It’s the "brute force" approach to sorting.

def bubble_sort(data):
    n = len(data)
    for i in range(n):
        for j in range(0, n - i - 1):
            if data[j] > data[j + 1]:
                data[j], data[j + 1] = data[j + 1], data[j]
    return data

If you're sorting a list of ten transaction amounts, this is fine. But if you're sorting ten thousand, you're going to hit a wall. The problem here is the nested loop. For every element in the list, we are potentially scanning the rest of the list. In big-O notation, we call this $O(n^2)$. I've seen developers try to "fix" this by adding a boolean flag to stop early if no swaps occurred, but that's just putting a band-aid on a fundamental architectural flaw. The cost grows quadratically; double your data, and you quadruple your wait time.

Trading memory for speed

To get a real performance boost, we have to stop thinking linearly and start thinking recursively. This is where Merge Sort comes in. Instead of swapping neighbors, Merge Sort uses a "divide and conquer" strategy. It splits the list in half, sorts those halves, and then merges them back together. It feels like more work upfront, but the efficiency gain is massive.

def merge_sort(data):
    if len(data) <= 1:
        return data

    mid = len(data) // 2
    left = merge_sort(data[:mid])
    right = merge_sort(data[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

Now we're talking $O(n \log n)$. This is a night-and-day difference. While Bubble Sort is struggling to breathe with 100,000 items, Merge Sort handles it with ease. But there's a catch—the trade-off is memory. Notice how I'm creating new lists (left, right, and result) during every recursive call? Bubble Sort sorts "in-place," meaning it uses almost no extra RAM. Merge Sort is memory-hungry. In a constrained environment, like an embedded system or a massive dataset that barely fits in memory, that trade-off becomes a critical decision.

The stability factor

One thing I want you to keep in mind is "stability." A sorting algorithm is stable if it preserves the original relative order of elements with equal keys. If you have a list of orders already sorted by date, and you then sort them by customer name using a stable sort (like Merge Sort), the orders for a single customer will still be in date order. Bubble Sort is also stable, but many "faster" algorithms, like Quick Sort, are not. When you're dealing with real-world objects rather than just integers, stability is often the difference between a feature that works and a bug that haunts your weekends.




📋 Practical Task

Implementing a Stable Merge Sort for Customer Order IDs

You have been given a list of tuples representing customer orders. Each tuple contains (order_id, priority). The list is already sorted by order_id, but you need to sort it by priority (where lower numbers mean higher priority) while maintaining the original order_id sequence for orders with the same priority.

Your Task: Implement the merge_sort_orders function using the Merge Sort logic discussed in the lesson. Ensure that your implementation is stable.

def merge_sort_orders(orders):
    # Your code here
    pass

# Test Case
orders = [
    (101, 2), 
    (102, 1), 
    (103, 2), 
    (104, 1), 
    (105, 3)
]

# Expected Result: [(102, 1), (104, 1), (101, 2), (103, 2), (105, 3)]
sorted_orders = merge_sort_orders(orders)
print(sorted_orders)
Rating
0 0

There are no comments for now.

to be the first to leave a comment.