Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes
Sponsored Reserved space — layout preview until AdSense is connected
Python

Sorting Lists in Python: What Actually Happens Under the Hood

Learn how Python's sorting works with Timsort, the key parameter, stable sorting, and practical techniques for sorting lists, dictionaries, and custom objects. Includes real-world examples and common mistakes to avoid.

July 2026 12 min read 1 views 0 hearts

When you call my_list.sort() or sorted(my_list), Python does something pretty clever behind the scenes. It doesn't just randomly shuffle items around until they're in order. Instead, it uses a sorting algorithm called Timsort — named after Tim Peters, the Python developer who created it in 2002.

But here's the thing: you don't need to know all the internals to use sorting effectively. What you do need is to understand a few practical concepts that will save you hours of debugging and make your code run faster.

The Two Ways to Sort

Python gives you two options, and they're not interchangeable:

list.sort() modifies the original list in place. It returns None, so you can't assign the result to a variable. This is memory-efficient because it doesn't create a copy.

numbers = [3, 1, 4, 1, 5, 9]
numbers.sort()
print(numbers)  # [1, 1, 3, 4, 5, 9]

sorted() returns a new sorted list, leaving the original untouched. Use this when you need to keep the original data.

numbers = [3, 1, 4, 1, 5, 9]
sorted_numbers = sorted(numbers)
print(numbers)        # [3, 1, 4, 1, 5, 9] - unchanged
print(sorted_numbers) # [1, 1, 3, 4, 5, 9]

The Key Parameter: Your Secret Weapon

Most beginners don't realize how powerful the key parameter is. It lets you sort by any transformation of your data without actually changing the data itself.

Say you have a list of strings and want to sort them by length:

words = ["python", "skillset", "code", "algorithm"]
words.sort(key=len)
print(words)  # ['code', 'python', 'skillset', 'algorithm']

The key function is called once per item, and the result is used for comparison. This is much more efficient than creating a new list of transformed values.

Sorting Dictionaries and Custom Objects

Here's where beginners often get stuck. You can't sort a dictionary directly — but you can sort its items:

scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
sorted_by_name = dict(sorted(scores.items()))
sorted_by_score = dict(sorted(scores.items(), key=lambda item: item[1]))

For custom objects, define a __lt__ method or use a key function:

class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

    def __repr__(self):
        return f"{self.name}: {self.grade}"

students = [Student("Alice", 85), Student("Bob", 92), Student("Charlie", 78)]
students.sort(key=lambda s: s.grade)
print(students)  # [Charlie: 78, Alice: 85, Bob: 92]

The Reverse Parameter

Simple but often overlooked: reverse=True gives you descending order. Combine it with key for powerful sorting:

words = ["apple", "banana", "cherry", "date"]
words.sort(key=len, reverse=True)
print(words)  # ['banana', 'cherry', 'apple', 'date']

Stable Sorting: What It Means and Why It Matters

Timsort is a stable sort. This means if two items have the same sort key, their original order is preserved. This is incredibly useful for multi-level sorting.

Imagine you have a list of students with names and grades, and you want to sort by grade first, then by name alphabetically:

students = [
    ("Alice", 85),
    ("Bob", 85),
    ("Charlie", 92),
    ("Diana", 78)
]

# First sort by name
students.sort(key=lambda s: s[0])
# Then sort by grade (stable sort preserves name order for same grades)
students.sort(key=lambda s: s[1])
print(students)
# [('Diana', 78), ('Alice', 85), ('Bob', 85), ('Charlie', 92)]

Notice how Alice and Bob both have grade 85, and their original alphabetical order is preserved. That's stability in action.

The operator Module: Cleaner Sorting

For common sorting patterns, Python's operator module makes your code more readable:

from operator import itemgetter, attrgetter

# For lists of tuples
data = [("apple", 3), ("banana", 1), ("cherry", 2)]
data.sort(key=itemgetter(1))  # Sort by the second element

# For lists of objects
students.sort(key=attrgetter('grade'))

Sorting in Reverse Without Confusion

The reverse parameter is straightforward, but beginners often mix it up with key:

numbers = [5, 2, 8, 1, 9]
numbers.sort(reverse=True)  # [9, 8, 5, 2, 1]

You can combine reverse with key for things like "sort by length, longest first":

words = ["python", "skillset", "code", "algorithm"]
words.sort(key=len, reverse=True)
print(words)  # ['algorithm', 'skillset', 'python', 'code']

What About Tuples and Strings?

Tuples are immutable, so you can't use .sort() on them. But sorted() works fine:

coordinates = (3, 1, 4, 1, 5)
sorted_coords = sorted(coordinates)  # [1, 1, 3, 4, 5]

Strings sort alphabetically by default, but case matters. Uppercase letters come before lowercase ones in ASCII order:

words = ["apple", "Banana", "cherry", "Date"]
sorted(words)  # ['Banana', 'Date', 'apple', 'cherry']

To sort case-insensitively, use key=str.lower:

sorted(words, key=str.lower)  # ['apple', 'Banana', 'cherry', 'Date']

The cmp_to_key Trick for Complex Comparisons

If you're migrating from Python 2 or need complex comparison logic, functools.cmp_to_key converts old-style comparison functions to key functions:

from functools import cmp_to_key

def compare_by_last_name(a, b):
    last_a = a.split()[-1]
    last_b = b.split()[-1]
    return (last_a > last_b) - (last_a < last_b)

names = ["Alice Johnson", "Bob Smith", "Charlie Brown"]
names.sort(key=cmp_to_key(compare_by_last_name))

Performance: What You Should Actually Care About

Timsort is remarkably efficient. It runs in O(n log n) time in the worst case and O(n) in the best case (when the list is already nearly sorted). For most real-world data, it's faster than quicksort or mergesort because it adapts to patterns in your data.

But here's what matters for your code: sorting is fast enough for lists up to about 100,000 items. Beyond that, you might notice a delay. For millions of items, consider using numpy or pandas which have optimized C-based sorting.

Common Mistakes Beginners Make

1. Forgetting that .sort() returns None

# Wrong
sorted_list = my_list.sort()  # sorted_list is None!

# Right
my_list.sort()
sorted_list = my_list  # or use sorted()

2. Sorting mixed types

Python 3 won't let you sort lists with incompatible types like strings and integers:

mixed = [1, "two", 3]  # TypeError on sort

3. Assuming sorting is case-sensitive by default

It is, but you usually want case-insensitive sorting. Use key=str.lower or key=str.upper.

Real-World Example: Sorting Log Entries

Here's a practical scenario from PythonSkillset's own codebase. We had log entries with timestamps and severity levels, and needed to sort them by severity (ERROR first) then by timestamp:

logs = [
    ("2024-01-15 10:30:00", "INFO", "Server started"),
    ("2024-01-15 10:31:00", "ERROR", "Connection timeout"),
    ("2024-01-15 10:32:00", "WARNING", "High memory usage"),
    ("2024-01-15 10:33:00", "ERROR", "Disk full"),
]

severity_order = {"ERROR": 0, "WARNING": 1, "INFO": 2}

# Sort by severity first, then by timestamp
logs.sort(key=lambda log: (severity_order[log[1]], log[0]))

This sorts all ERROR entries first (in timestamp order), then WARNING, then INFO. The tuple key lets you sort by multiple criteria in one pass.

When Sorting Gets Tricky: Locale and Custom Order

Sorting strings with accented characters or in a specific language requires the locale module:

import locale
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
words = ["äpfel", "apfel", "Äpfel", "Apfel"]
sorted(words, key=locale.strxfrm)  # Correct German sorting

For custom sort orders, like sorting days of the week, use a dictionary as a key:

days = ["Monday", "Wednesday", "Tuesday", "Friday", "Thursday"]
day_order = {day: i for i, day in enumerate(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"])}
days.sort(key=lambda d: day_order[d])
print(days)  # ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']

The One-Liner That Confuses Everyone

You'll often see this pattern in Python codebases:

sorted_list = sorted(original, key=lambda x: x[1])

The lambda creates a tiny anonymous function. It's equivalent to:

def get_second_element(x):
    return x[1]
sorted_list = sorted(original, key=get_second_element)

Use lambdas for simple cases, but for complex logic, define a named function. Your future self will thank you.

What About list.sort() vs sorted() Performance?

For most cases, the performance difference is negligible. But list.sort() is slightly faster because it doesn't create a new list. If you're sorting millions of items, use list.sort() when you don't need the original order.

A Real-World Example from PythonSkillset

At PythonSkillset, we once had a bug where user activity logs were sorted incorrectly. The issue was that timestamps were strings, not datetime objects. Sorting strings alphabetically doesn't work for dates:

# Wrong: string sorting
logs = ["2024-01-15", "2024-01-02", "2024-01-10"]
logs.sort()  # ['2024-01-02', '2024-01-10', '2024-01-15'] - correct by luck

# But with different formats:
logs = ["2024-01-15", "2024-01-2", "2024-01-10"]
logs.sort()  # ['2024-01-10', '2024-01-15', '2024-01-2'] - wrong!

The fix was to convert to proper datetime objects first:

from datetime import datetime
logs.sort(key=lambda x: datetime.strptime(x, "%Y-%m-%d"))

The __lt__ Method: How Python Compares Objects

When you sort custom objects, Python uses the __lt__ (less than) method. By default, it doesn't exist for your classes, so sorting will fail. You can define it:

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __lt__(self, other):
        return self.price < other.price

    def __repr__(self):
        return f"{self.name}: ${self.price}"

products = [Product("Laptop", 1200), Product("Mouse", 25), Product("Keyboard", 80)]
products.sort()
print(products)  # [Mouse: $25, Keyboard: $80, Laptop: $1200]

The One Thing That Trips Up Everyone

Here's a bug that PythonSkillset sees in code reviews all the time:

# Wrong: sorting a list of strings that look like numbers
values = ["10", "2", "1", "20"]
values.sort()
print(values)  # ['1', '10', '2', '20'] - string sorting!

# Right: convert to integers first
values.sort(key=int)
print(values)  # ['1', '2', '10', '20']

When Not to Use Python's Sort

If you're working with truly massive datasets (millions of items), Python's built-in sort might not be your best option. Consider:

  • numpy for numerical arrays: numpy.sort() is much faster
  • pandas for DataFrames: df.sort_values() handles complex sorting elegantly
  • External sorting for data that doesn't fit in memory

But for 99% of use cases, Python's built-in sort is more than sufficient. It's been optimized for over two decades and handles real-world data patterns remarkably well.

The One-Liner That Impresses in Code Reviews

Need to sort a list of dictionaries by a specific key? This is a common pattern:

users = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Charlie", "age": 35}
]
users.sort(key=lambda u: u["age"])

Or even cleaner with operator.itemgetter:

from operator import itemgetter
users.sort(key=itemgetter("age"))

What About Sorting in Reverse Alphabetical Order?

Just combine reverse=True with your key:

words = ["apple", "banana", "cherry", "date"]
words.sort(key=len, reverse=True)  # Longest first

The Most Common Mistake: Sorting in Place and Assigning

This is the number one sorting bug in PythonSkillset's codebase:

# Wrong
result = my_list.sort()  # result is None!

# Right
my_list.sort()
result = my_list
# Or
result = sorted(my_list)

Performance Tips for Real-World Code

  1. Precompute keys if the key function is expensive. Instead of key=expensive_function, compute the key once and store it in a tuple.

  2. Use operator.itemgetter instead of lambdas for simple attribute access — it's faster.

  3. Sort in place when you don't need the original order. It saves memory.

  4. For nearly sorted data, Timsort is incredibly fast. Don't overthink it.

The Bottom Line

Python's sorting is powerful, stable, and fast. The key things to remember are:

  • Use list.sort() for in-place sorting, sorted() for new lists
  • The key parameter is your best friend for custom sorting
  • Stable sorting lets you chain multiple sorts
  • For most real-world data, Timsort is already optimal

Next time you write my_list.sort(), you'll know there's a sophisticated algorithm working for you — but you don't need to understand every detail to use it effectively. Just remember the key parameter, and you'll be sorting like a pro.

Comments

Questions, corrections, and tips stay visible for everyone reading this page.

0 in thread

Join the discussion

Shown next to your comment.

Up to 4,000 characters

No comments yet

Be the first to leave a note — it helps the next reader.