Sort Data with sorted() & Keys
Learn to sort lists and custom objects using Python's sorted() function, including custom key functions for advanced ordering.
Focus: sort data with sorted and custom keys
Sorting a list of numbers or strings in Python seems straightforward — until you need to order a list of dictionaries by a specific key, sort strings by their length, or organize custom objects by an attribute. Without a solid understanding of sorted() and its key parameter, you'll end up writing clumsy loops or complex comparators that quickly become unmaintainable. Today you'll master the Pythonic way to sort any iterable with custom logic, a skill that appears in everything from data analysis pipelines to API response formatting.
The problem this lesson solves
Imagine you have a list of dictionaries representing employees:
employees = [
{'name': 'Alice', 'age': 30, 'salary': 75000},
{'name': 'Bob', 'age': 25, 'salary': 68000},
{'name': 'Charlie', 'age': 35, 'salary': 82000}
]
You need to sort by salary in ascending order. Without sorted() and the key parameter, you'd likely write a manual insertion sort or a for loop with an inner comparison — both error-prone and slow for larger datasets. The built-in list.sort() method modifies the list in place, but you often want a new sorted list without mutating the original. More critically, neither list.sort() nor sorted() natively understand how to compare dictionaries. This is the exact pain point that sorted() with custom keys solves: it gives you a clean, single-line solution to order any collection based on any computed value.
Core concept / mental model
Think of sorted() as a factory that takes your data and returns a new, ordered list. The key parameter is a "transformer" — a function that defines how each element should be compared, but does not change the original elements themselves.
Visualize it: Imagine you're sorting a deck of cards by their suit, but you only assign chips that represent the suit rank (e.g., hearts → 1, spades → 4). The
keyfunction is like assigning those chips — the sorted order is based on the chips, but you still get back the original cards.
Definitions
sorted(iterable, *, key=None, reverse=False): Returns a new sorted list from the elements of any iterable. Thekeyparameter specifies a function of one argument that is used to extract a comparison key from each element.keyfunction: Runs once per element during sorting. Its return value is used for comparison, not the raw element. Common key functions includelen,str.lower,itemgetter,attrgetter, or lambda functions.reverse=True: If you need descending order, passreverse=True.
Key mental model: The key is a projection, not a remapping
# Without key — sorting strings by default (lexicographic)
sorted(['apple', 'Banana', 'Cherry'])
# Output: ['Banana', 'Cherry', 'apple'] # Uppercase before lowercase
# With key — sorting by lowercase version
sorted(['apple', 'Banana', 'Cherry'], key=str.lower)
# Output: ['apple', 'Banana', 'Cherry'] # Case-insensitive
The str.lower key transforms each element before comparison but doesn't modify the actual strings.
How it works step by step
Let's break down what happens when you run sorted(['a', 'BB', 'ccc'], key=len):
- Create a temporary list — Python internally creates a list of tuples
(key_value, original_element)for each item. So for'a'(len=1),'BB'(len=2),'ccc'(len=3), it builds[(1, 'a'), (2, 'BB'), (3, 'ccc')]. - Sort the temporary list — This list is sorted lexicographically based on the first tuple element (the key). The result:
[(1, 'a'), (2, 'BB'), (3, 'ccc')]. - Extract original elements — Python discards the key parts and returns
['a', 'BB', 'ccc']in sorted order.
Pro tip: If you need the sorted order to be stable (elements with equal keys preserve their original order),
sorted()already guarantees stable sort in Python 3.10+.
Step-by-step guide to writing custom key functions
- Identify the comparison value you need to extract from each element.
- Write a function that takes one element and returns that comparison value.
- Pass that function as the
keyargument tosorted().
# Step 1: We want to sort tuples by the second element
records = [(1, 'Z'), (2, 'A'), (3, 'M')]
# Step 2: Key function extracts the second item
def by_second(item):
return item[1]
# Step 3: Use it
print(sorted(records, key=by_second))
# Output: [(2, 'A'), (3, 'M'), (1, 'Z')]
Hands-on walkthrough
Example 1: Sorting a list of strings by length
fruits = ['kiwi', 'apple', 'banana', 'pear']
sorted_fruits = sorted(fruits, key=len)
print(sorted_fruits)
# Output: ['kiwi', 'pear', 'apple', 'banana']
Example 2: Sorting dictionaries by a nested value
students = [
{'name': 'Alice', 'grades': [85, 92, 78]},
{'name': 'Bob', 'grades': [90, 88, 95]},
{'name': 'Charlie', 'grades': [70, 80, 90]}
]
# Sort by average grade (descending)
sorted_students = sorted(students, key=lambda s: sum(s['grades']) / len(s['grades']), reverse=True)
print(sorted_students)
# Output:
# [{'name': 'Bob', 'grades': [90, 88, 95]},
# {'name': 'Alice', 'grades': [85, 92, 78]},
# {'name': 'Charlie', 'grades': [70, 80, 90]}]
Example 3: Sorting objects with attributes
class Book:
def __init__(self, title, year):
self.title = title
self.year = year
def __repr__(self):
return f"Book({self.title!r}, {self.year})"
books = [
Book('1984', 1949),
Book('Brave New World', 1932),
Book('Fahrenheit 451', 1953)
]
# Sort by year using operator.attrgetter
from operator import attrgetter
sorted_books = sorted(books, key=attrgetter('year'))
print(sorted_books)
# Output: [Book('Brave New World', 1932), Book('1984', 1949), Book('Fahrenheit 451', 1953)]
Example 4: Sorting with multiple levels (tie-breakers)
employees = [
('Alice', 'Engineering', 75000),
('Bob', 'Engineering', 68000),
('Charlie', 'Sales', 82000),
('Diana', 'Sales', 68000)
]
# Sort first by department alphabetically, then by salary descending
from operator import itemgetter
# Using a tuple as key — Python sorts tuples lexicographically
sorted_employees = sorted(employees, key=lambda e: (e[1], -e[2]))
print(sorted_employees)
# Output:
# [('Bob', 'Engineering', 68000),
# ('Alice', 'Engineering', 75000),
# ('Diana', 'Sales', 68000),
# ('Charlie', 'Sales', 82000)]
Note the trick:
-e[2]negates the salary to simulate descending order within ascending department sort.
Compare options / when to choose what
| Method | Use case | Returns | Mutates original? | Key support |
|---|---|---|---|---|
sorted() |
Need a new list, work with any iterable | New list | No | Yes |
list.sort() |
Only on lists, modify in place | None |
Yes | Yes |
sorted(..., reverse=True) |
Descending order | New list | No | Yes (negate key or use reverse) |
heapq.nsmallest/nlargest |
Top N elements only (large data) | List of N items | No | Yes (key parameter) |
When to use sorted():
- You want to keep the original list intact.
- You're sorting tuples, sets, or generators (anything that doesn't have .sort()).
- You need a one-liner in a functional pipeline.
When to use list.sort():
- You're sorting a list and don't need the original order.
- Memory is a concern (no extra list created).
When to use heapq:
- You only need the 5 smallest items from a list of 10,000 — more efficient than full sort.
Troubleshooting & edge cases
Common pitfalls
-
Forgetting that
list.sort()returnsNonepython my_list = [3, 1, 2] result = my_list.sort() # result is None! print(result) # None -
Trying to sort non-comparable types
python mixed = [1, 'two', 3] # sorted(mixed) # TypeError: '<' not supported between instances of 'str' and 'int' # Fix: Provide a key that returns consistent types sorted(mixed, key=str) # Works, but converts all to strings -
Mutable default keys
python def bad_key(x): return {x: 1} # Dictionaries are unordered and unhashable # sorted(['a', 'b'], key=bad_key) # TypeError: 'dict' is unhashable
Case-sensitivity and locale handling
# Default sorting is case-sensitive ('A' < 'a' in ASCII)
sorted(['apple', 'Banana', 'cherry'])
# Output: ['Banana', 'apple', 'cherry']
# Case-insensitive: use str.casefold (Python 3.10+ handles some Unicode)
sorted(['apple', 'Banana', 'cherry'], key=str.casefold)
# Output: ['apple', 'Banana', 'cherry']
What you learned & what's next
You now understand that sorted() is the Swiss Army knife for ordering data in Python. You learned:
- Use
sorted(iterable)for a new list,list.sort()for in-place mutation. - The
keyparameter accepts any callable (function, lambda, operator module) to extract a comparison value. - Tuples as keys enable multi-level sorting with tie-breakers.
reverse=Trueflips the order.- Always ensure your key function returns comparable types.
Next step: In the next lesson, you'll learn how to sort data with sorted and custom keys, applying these concepts to real-world data like API responses and CSV datasets. Practice by sorting a list of dictionaries by a nested list's length — a common interview question.
Remember: Sorting is not just about ordering — it's about bringing structure to chaos, one key at a time.
Practice recap
Quick exercise: Write a function sort_by_second_char that takes a list of strings and returns a new list sorted by the second character of each string (case-insensitive). For strings shorter than 2 characters, place them at the end. Test it with ['hello', 'a', 'ABC', 'xYz'] — the result should be ['a', 'ABC', 'hello', 'xYz']. This forces you to handle edge cases in your key function.
Common mistakes
- Calling
list.sort()and assigning the result to a variable:result = my_list.sort()setsresulttoNone, not the sorted list. - Using a mutable object like a list or dictionary as the return value of a key function — Python requires hashable/comparable keys.
- Forgetting that sorted strings default to case-sensitive ASCII ordering, not natural human alphabetic order — use
key=str.lowerorstr.casefoldfor case-insensitive sorts.
Variations
- Use
sorted(..., reverse=True)for descending order instead of manually negating numeric keys. - Use
functools.cmp_to_key()if you need to convert an old-style comparison function (from Python 2) to a key function. - For very large datasets, consider
numpy.sortfor numeric arrays orpandas.DataFrame.sort_valuesfor tabular data.
Real-world use cases
- Sorting e-commerce products by price ascending, then by review score descending for a product listing API.
- Ordering log entries by timestamp (string) parsed with a key function, then by error severity in a monitoring dashboard.
- Sorting tournament leaderboard data by score descending, with ties broken by number of games played ascending.
Key takeaways
- Use
sorted()to create a new sorted list without mutating the original; uselist.sort()for in-place sorting. - The
keyparameter takes a callable that returns a comparison value for each element — never modifies elements themselves. - Return a tuple from a key function for multi-level sorting (first by first element, then second, etc.).
- Always ensure key function returns comparable types (e.g., all strings or all numbers, not a mix).
- For descending order, use
reverse=Trueor negate numeric keys in the tuple.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.