Whether you’re wrangling a simple list or a complex set of objects, this guide will teach you how to sort data in Python like a pro.
Sorting data is a fundamental task in Python, and while it might seem straightforward, there are nuances that can make a big difference in your code.
In this post, we’ll explore Python’s sorting mechanisms — from the classic .sort() method to the flexible sorted() function — and share practical examples along the way.
Whether you’re organizing a list of names, preparing data for analysis, or just curious about Python’s efficient Timsort algorithm, this guide is designed with you in mind.
Let’s take a closer look at how sorting works in Python and why choosing the right approach matters.
By the way, here's the video version of this article. Perfect if you want a short, high level overview before diving into this more in depth article.
Python offers two primary tools for sorting: the .sort() method and the .sorted() function. While they serve a similar purpose, each comes with its own characteristics that make them suitable for different scenarios.
The .sort() method is available exclusively on lists. Here’s what you need to know:
Consider this example:
numbers = [3, 1, 4, 1, 5, 9, 2]
print("Before sorting:", numbers)
# Before sorting: [3, 1, 4, 1, 5, 9, 2]
# Sorting the list in place
numbers.sort()
print("After sorting:", numbers)
# After sorting: [1, 1, 2, 3, 4, 5, 9]
In this snippet, notice how calling numbers.sort() changes the list itself rather than producing a new sorted list.
In contrast, the sorted() function works with any iterable and returns a new, sorted list. Here are the key points:
For example:
words = "the quick brown fox".split()
print("Original words:", words)
# Original words: ['the', 'quick', 'brown', 'fox']
# Using sorted() to create a new sorted list
sorted_words = sorted(words)
print("Sorted words:", sorted_words)
print("Original words:", words)
# Sorted words: ['brown', 'fox', 'quick', 'the']
# Original words: ['the', 'quick', 'brown', 'fox']
Here, the words list remains unchanged, while sorted_words holds the sorted result.
The choice between .sort() and sorted() generally depends on whether you need to preserve the original order of your data:
.sort() is ideal when you’re comfortable modifying the existing list in place.
sorted() is the better option if you need to retain the original data or if you’re working with a non-list iterable.
Both methods are integral to Python’s toolkit, and understanding their differences will help you write more efficient and effective code.
What if your data isn’t stored in a list? That’s where the sorted() function comes into play. Unlike .sort(), sorted() accepts any iterable and returns a new list containing the sorted items, leaving your original data untouched.
Consider a tuple as an example:
# A tuple of numbers
numbers_tuple = (10, 4, 2, 8, 6)
print("Original tuple:", numbers_tuple)
# Original tuple: (10, 4, 2, 8, 6)
# Using sorted() to get a new sorted list
sorted_numbers = sorted(numbers_tuple)
print("Sorted list from tuple:", sorted_numbers)
# Sorted list from tuple: [2, 4, 6, 8, 10]
Here, the original tuple remains unchanged while sorted_numbers holds the sorted output. This approach is especially useful when you need to maintain the integrity of your original data structure.
Another interesting case is sorting the characters in a string. Although it might not be an everyday task, it’s a great demonstration of sorted()’s flexibility:
# A string to be sorted alphabetically
unsorted_string = "python"
sorted_characters = sorted(unsorted_string)
print("Sorted characters:", sorted_characters)
# Sorted characters: ['h', 'n', 'o', 'p', 't', 'y']
# If desired, join the sorted characters back into a string
sorted_string = ''.join(sorted_characters)
print("Sorted string:", sorted_string)
# Sorted string: hnopty
This example shows how you can transform a string into a list of characters, sort them, and even convert them back into a string if needed.
Sorting isn’t always about arranging things in their natural, ascending order. Sometimes, you need to flip the script and view your data in reverse. Whether you’re after a descending order of numbers or simply prefer the highest values first, Python’s sorting methods make this a breeze.
By default, both the .sort() method and the sorted() function arrange items in ascending order. However, a simple tweak lets you switch to descending order by using the reverse parameter.
Take a look at this example using a list of numbers:
# A list of numbers in random order
numbers = [7, 2, 9, 4, 3]
print("Original list:", numbers)
# Original list: [7, 2, 9, 4, 3]
# Sorting in descending order
numbers.sort(reverse=True)
print("Descending order:", numbers)
# Descending order: [9, 7, 4, 3, 2]
In this snippet, setting reverse=True tells Python to flip the default order.
Sorting order might seem like a minor detail, but it can have a big impact on the readability and usefulness of your data. For example, if you’re displaying scores on a leaderboard, you typically want the highest scores at the top. In contrast, chronological data might make more sense in ascending order.
The beauty of Python’s sorting functions is that switching between ascending and descending is as simple as changing one parameter. This gives you the flexibility to present your data in the way that makes the most sense for your application.
Up to this point, we’ve been relying on Python to decide how things should be sorted. But what if you want a little more control? What if you don’t just want to sort numbers or strings, but something a little more nuanced—like sorting words by length, or ignoring case, or sorting complex data structures by one of their components?
That’s where the key parameter comes in. It lets you steer the sorting behavior by telling Python what to sort by.
Let’s start with a simple example: sorting a list of words by their length rather than alphabetical order.
words = ["banana", "kiwi", "apple", "cherry", "fig"]
# Sort the list by the length of each word
sorted_by_length = sorted(words, key=len)
print("Sorted by length:", sorted_by_length)
# Sorted by length: ['fig', 'kiwi', 'apple', 'banana', 'cherry']
Here, we’re telling sorted() to use the len() function as the basis for comparison. It doesn’t sort the words themselves directly—it sorts the result of calling len() on each word. Nice and simple.
By default, Python’s sort is case-sensitive, which can lead to what looks like a strange order. If you want to sort strings while ignoring case, you can pass in a function like str.lower as the key.
names = ["alice", "Bob", "charlie", "Diana"]
# Default sort, case-sensitive
sensitive_names = sorted(names)
print("Case-sensitive sort: ", sensitive_names)
# Case-sensitive sort: ['Bob', 'Diana', 'alice', 'charlie']
# Sort alphabetically, ignoring case
sorted_names = sorted(names, key=str.lower)
print("Case-insensitive sort:", sorted_names)
# Case-insensitive sort: ['alice', 'Bob', 'charlie', 'Diana']
This ensures "Bob" doesn’t randomly leapfrog "alice" just because of the capital “B”.
What’s happening under the hood is that Python uses the function you pass to key to transform each element before sorting. It doesn’t change the data itself — just what Python looks at when making comparisons.
So you can use any function — built-in or custom — that takes a single item and returns something sortable. It could be the length of a word, a specific field in a dictionary, or even the result of a more complex operation.
It’s worth noting that key and reverse can be used together:
words = ["banana", "kiwi", "apple", "cherry", "fig"]
# Longest words first
sorted_longest_first = sorted(words, key=len, reverse=True)
print("Longest to shortest:", sorted_longest_first)
# Longest to shortest: ['banana', 'cherry', 'apple', 'kiwi', 'fig']
The key function decides what to sort by, and reverse decides in what order.
Tuples are a common way to store structured data in Python. Think of them like lightweight records — compact, immutable, and often used when you want to group related values together.
And just like with lists or strings, you can sort collections of tuples using sorted() — especially when you combine it with the key parameter. Let’s take a look.
If you try to sort a list of tuples without any additional parameters, Python will sort them by the first element in each tuple. If there’s a tie, it moves on to the second element, and so on. It’s polite like that.
people = [("Alice", 30), ("Bob", 25), ("Charlie", 35)]
# Sorts by name (the first element)
sorted_people = sorted(people)
print("Sorted by name:", sorted_people)
# Sorted by name: [('Alice', 30), ('Bob', 25), ('Charlie', 35)]
In this case, Python sorts the tuples alphabetically by the names.
Often what you really want is to sort by something other than the first item — like age, in this case. That’s where the key parameter comes in again.
# Sort by age (the second element in each tuple)
sorted_by_age = sorted(people, key=lambda person: person[1])
print("Sorted by age:", sorted_by_age)
# Sorted by age: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]
Here, the lambda function tells Python: “Hey, for each person tuple, focus on the second item (the age) when sorting.” The original order is untouched, and the output is neatly arranged by age.
You can also sort by more than one value — say, by age and then name — using a tuple as your sort key:
# Sort by age, then by name alphabetically
sorted_complex = sorted(people, key=lambda person: (person[1], person[0]))
print("Sorted by age, then name:", sorted_complex)
# Sorted by age, then name: [('Bob', 25), ('Alice', 30), ('Charlie', 35)]
This tells Python to first compare the ages, and if two people have the same age, use the name to break the tie.
This pattern shows up all the time — whether you’re dealing with coordinate pairs, (name, score) pairs, or any small structured dataset.
Dictionaries are great for organizing data as key-value pairs, but they don't maintain a natural "order" the same way lists or tuples do — at least not when it comes to sorting. So if you’ve ever tried to call .sort() on a dictionary and gotten a confused error message, you’re not alone.
Let’s walk through how to sort dictionaries in Python and what you can (and can’t) do with them.
First things first: starting with Python 3.7, dictionaries preserve the insertion order of keys. That means they remember the order in which items were added, but that’s not the same as being sorted.
If you want your dictionary sorted by keys or values, you'll need to extract those parts into something that can be sorted—like a list of items.
Let’s start with a basic dictionary:
scores = {"Alice": 85, "Bob": 92, "Charlie": 78}
If you want to sort the dictionary by the keys (the names), you can do this:
# Sort by key (name)
sorted_by_name = sorted(scores.items())
print("Sorted by name:", sorted_by_name)
# Sorted by name: [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
This gives you a list of tuples sorted alphabetically by name. It’s not a dictionary anymore, but often that’s what you want when displaying or processing data.
If you really need it back as a dictionary then you can just convert the tuple like so:
# Convert back to dict
sorted_dict_by_name = dict(sorted_by_name)
print(sorted_dict_by_name)
# {'Alice': 85, 'Bob': 92, 'Charlie': 78}
Now let’s sort by the actual scores (the values). That’s where the key parameter comes in handy again:
# Sort by value (score)
sorted_by_score = sorted(scores.items(), key=lambda item: item[1])
print("Sorted by score:", sorted_by_score)
# Sorted by score: [('Charlie', 78), ('Alice', 85), ('Bob', 92)]
Same idea: we’re using a lambda to sort by the second item in each key-value tuple.
And yes—you can go full circle and turn this back into a dictionary too:
sorted_dict_by_score = dict(sorted_by_score)
print(sorted_dict_by_score)
# {'Charlie': 78, 'Alice': 85, 'Bob': 92}
Just keep in mind that sorting returns a new object; dictionaries themselves don’t support in-place sorting.
Just like with lists and tuples, you can sort in descending order using reverse=True:
# Top scores first
top_scores = sorted(scores.items(), key=lambda item: item[1], reverse=True)
print("Top scores:", top_scores)
# Top scores: [('Bob', 92), ('Alice', 85), ('Charlie', 78)]
Sorting a dictionary is especially useful when you're preparing data for display — like leaderboards, rankings, frequency counts, or any time you're presenting information in a user-friendly format.
So far, we've sorted strings, numbers, tuples, and even dictionary items. But what about custom objects—your own classes, your own data? The good news is: Python doesn’t treat those any differently. The trick is to tell sorted() how to extract the bit you actually want to sort by.
Let’s break it down.
Suppose you have a Person class:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
people = [
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 35)
]
Now let’s say you want to sort this list by age. Since Python doesn’t know how to compare two Person objects out of the box, you need to give it a hint using the key parameter:
# Sort by age
sorted_by_age = sorted(people, key=lambda person: person.age)
for p in sorted_by_age:
print(p.name, p.age)
# Bob 25
# Alice 30
# Charlie 35
This is just like sorting a list of tuples by a specific index — except now, you're accessing an attribute instead.
If you prefer something a little cleaner or more performant than a lambda (especially when sorting large datasets), Python’s operator module has you covered:
from operator import attrgetter
# Sort by name using attrgetter
sorted_by_name = sorted(people, key=attrgetter("name"))
for p in sorted_by_name:
print(p.name, p.age)
# Alice 30
# Bob 25
# Charlie 35
Functionally, this is identical to using a lambda, but it reads a bit more declaratively — especially useful when chaining or sorting by multiple fields.
Sometimes, one attribute just isn’t enough. You might want to sort by age, and then by name alphabetically as a tiebreaker. No problem:
# Sort by age, then name
sorted_by_age_then_name = sorted(people, key=lambda p: (p.age, p.name))
for p in sorted_by_age_then_name:
print(p.name, p.age)
# Bob 25
# Alice 30
# Charlie 35
Or with attrgetter, you can pass multiple fields:
sorted_by_age_then_name = sorted(people, key=attrgetter("age", "name"))
When you're working on real-world problems—sorting users, transactions, blog posts, you name it — you're often dealing with custom classes. Knowing how to sort them effectively is essential for anything from user interfaces to backend processing.
By now, you’ve probably noticed that lambda functions have been popping up all over the place. That’s because they’re incredibly handy for sorting. In fact, when it comes to quick, one-off sorting logic, lambda is the Swiss Army knife of the key parameter.
Let’s explore a few examples to round out your sorting toolkit.
A lambda is just a small anonymous function. It’s perfect when you need a simple function for something like sorting but don’t want to define a whole named function.
For example:
sorted(['apple', 'banana', 'kiwi'], key=lambda fruit: len(fruit))
# ['kiwi', 'apple', 'banana']
This sorts the fruits by the length of each word — shortest to longest.
For more complex sorting logic, you can use a regular named function instead of a lambda:
def last_digit(n):
return n % 10
sorted_numbers = sorted(numbers, key=last_digit)
This is especially helpful when your sorting rule gets too long or specific for a lambda to stay readable.
Use a lambda when:
Avoid it when:
So far, we’ve treated sorting like a high-level, friendly tool — just pass in a list and a key, and voilà, things are magically in order. But before we wrap up, it’s worth understanding what’s going on behind the scenes.
Python’s built-in sort() and sorted() both use an algorithm called Timsort. It's a hybrid sorting algorithm derived from merge sort and insertion sort. Without getting too academic:
The takeaway? Python’s sort is fast. You rarely need to reach for your own custom sorting algorithm—this one is very well-behaved.
Python’s sort is stable, and that’s a big deal.
What does that mean?
A stable sort preserves the relative order of elements that compare as equal.
Let’s look at an example:
data = [
{"name": "Alice", "score": 90},
{"name": "Bob", "score": 90},
{"name": "Charlie", "score": 85},
]
# Sort by score
sorted_data = sorted(data, key=lambda x: x["score"])
# [{'name': 'Charlie', 'score': 85}, {'name': 'Alice', 'score': 90}, {'name': 'Bob', 'score': 90}]
Alice and Bob both have a score of 90. Because sorting is stable, their original order is preserved in the sorted list.
This is especially important when you're doing multi-pass sorting — for example, sorting by one field, then by another. You can sort by secondary criteria first, then sort by primary criteria, and the stability will make it all work as expected.
A few quick tips to keep in mind:
Sorting a list with mixed data types (e.g. strings and integers) will raise a TypeError in Python 3.
For example:
sorted([1, "banana", 3])
# TypeError: '<' not supported between instances of 'str' and 'int'
Python 2 used to let you do this, but Python 3 wisely insists that the things you're comparing must be comparable. Always make sure your data is clean and consistent before sorting.
Sorting may not be the flashiest skill in your toolkit, but it’s one of those practical superpowers you’ll reach for time and time again — whether you're tidying up data, ranking results, or simply getting a list into some kind of order that makes sense to actual humans.