Sorted() and Custom Keys
Use sorted() with custom key functions to organize Python data flexibly. This tutorial shows how to sort lists, dicts, and more by specified criteria like string length or nested values.
Focus: sort data with sorted() and custom keys
Ever sorted a list of tuples only to get results ordered by the first element when you really wanted to sort by the second, third, or some computed value? Python’s sorted() function is powerful, but without custom keys you're stuck with default behavior — sorting by the whole item. This lesson unlocks sorted() with custom keys, giving you the precision to sort lists of dictionaries, objects, and complex nested data exactly how you need.
The problem this lesson solves
Imagine you have a list of dictionaries representing employees, each with name, age, salary, and department. You need to print the list sorted by salary descending, but the default sorted() sorts dictionaries by dictionary order — which is a TypeError. Or you have a list of strings and you want them sorted by length, not alphabetically. Default sorting falls short.
The pain points:
- Default sorting works on simple lists of numbers or strings, but fails with complex data.
- Sorting by criteria like
name,age, or a computed value requires specifying what to sort by. - Reversing order is easy, but customizing the sort field isn't obvious at first.
This lesson shows you how to use the key parameter of sorted() (and list.sort()) to sort any iterable by a function that returns a single sort key per element.
Core concept / mental model
Think of sorted() as a machine that re-orders items in a list. The key is a lens — a small function applied to each item before comparing. The machine sees the transformed value (the key) and sorts based on that, but returns the original items in the new order.
Example:
- Raw items: ["banana", "fig", "apple"]
- Key function: len (length of each string)
- Transformed keys: [6, 3, 5]
- Sorted by key: ["fig", "apple", "banana"]
fruits = ["banana", "fig", "apple"]
sorted_fruits = sorted(fruits, key=len)
print(sorted_fruits) # ['fig', 'apple', 'banana']
The key function is called once per element (efficient, Python caches it), and the sort uses those cached values. This is the foundation for all custom sorting in Python.
How it works step by step
- Create a sequence — a list, tuple, or any iterable.
- Define a key function — a callable that takes one argument (an element) and returns a value to sort by. This can be a built-in function, a lambda, or a named function.
- Call
sorted()— pass the iterable andkey=your_function. - Get a new sorted list — original unchanged (immutable). If you want in-place sorting, use
list.sort().
Key function types:
- Built-in functions:
len,str.lower,int,float, etc. - Lambda:
lambda x: x[1]for sorting tuples by second element. - Operator module:
operator.itemgetter(1)is faster for large data. - Custom function: for complex logic like sorting by a nested key.
Example — sorting tuples by second element:
data = [(1, 'z'), (2, 'a'), (3, 'm')]
sorted_by_second = sorted(data, key=lambda x: x[1])
print(sorted_by_second) # [(2, 'a'), (3, 'm'), (1, 'z')]
Example — sorting dictionaries by a specific key:
employees = [
{'name': 'Alice', 'age': 30, 'salary': 60000},
{'name': 'Bob', 'age': 25, 'salary': 50000},
{'name': 'Charlie', 'age': 35, 'salary': 70000}
]
sorted_by_salary = sorted(employees, key=lambda emp: emp['salary'])
print(sorted_by_salary)
# [{'name': 'Bob', ...}, {'name': 'Alice', ...}, {'name': 'Charlie', ...}]
Example — using operator.itemgetter:
from operator import itemgetter
sorted_by_age = sorted(employees, key=itemgetter('age'))
print(sorted_by_age)
# [{'name': 'Bob', ...}, {'name': 'Alice', ...}, {'name': 'Charlie', ...}]
Hands-on walkthrough
Let’s sort a list of strings by their last character:
words = ['apple', 'banana', 'cherry', 'date']
sorted_by_last = sorted(words, key=lambda w: w[-1])
print(sorted_by_last)
# ['banana', 'apple', 'cherry', 'date']
Now sort a list of objects (namedtuples) by a field:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
people = [Person('Alice', 30), Person('Bob', 25), Person('Charlie', 35)]
sorted_by_age = sorted(people, key=lambda p: p.age)
print(sorted_by_age)
# [Person(name='Bob', age=25), Person(name='Alice', age=30), Person(name='Charlie', age=35)]
Sorting by multiple criteria:
Use a tuple as the key — Python sorts lexicographically (first element, then second if tie).
employees = [
{'name': 'Alice', 'age': 30, 'salary': 60000},
{'name': 'Bob', 'age': 25, 'salary': 50000},
{'name': 'Alice', 'age': 35, 'salary': 60000},
]
sorted_employees = sorted(employees, key=lambda e: (e['name'], e['age']))
print(sorted_employees)
# [{'name': 'Alice', 'age': 30, ...}, {'name': 'Alice', 'age': 35, ...}, {'name': 'Bob', 'age': 25, ...}]
Reverse order:
Add reverse=True to sort descending.
sorted_desc_salary = sorted(employees, key=lambda e: e['salary'], reverse=True)
Compare options / when to choose what
| Method | When to use | Example |
|---|---|---|
sorted() with lambda |
Simple one-off logic, readability okay for short lines | sorted(data, key=lambda x: x[1]) |
sorted() with operator module |
Performance-critical large data, multiple fields | sorted(data, key=itemgetter(1)) |
sorted() with custom function |
Complex conditions, reuse across modules | sorted(data, key=my_sort_func) |
list.sort() |
In‑place sorting, saving memory | my_list.sort(key=len) |
sorted() with reverse=True |
Descending order quickly | sorted(data, reverse=True) |
Performance tip: operator.itemgetter is faster than a lambda for simple attribute access, especially on large lists (10,000+ items). For one-off or small lists, readability matters more.
Troubleshooting & edge cases
Problem: TypeError: 'dict' object is not callable
You accidentally used a dict as the key argument (e.g., key=my_dict instead of key=lambda x: my_dict[x]). Ensure the key is a callable.
Problem: Key function raises an exception on some elements If your key function expects a certain type or field, make sure all elements support it. Use a default or filter first.
# Risky — if any dict lacks 'age', AttributeError
data = [{'name': 'Alice'}, {'name': 'Bob', 'age': 25}]
sorted(data, key=lambda x: x['age']) # Error!
Fix: Use .get() with a default:
sorted(data, key=lambda x: x.get('age', 0))
Edge case: Sorting by a method that returns None
list.sort() returns None — it modifies the list in place. Do not assign its result.
Edge case: Sorting dictionaries directly (Python 3.6+ is insertion-order)
dict itself is not directly sortable (it’s not a sequence). Use sorted(dict.items()) to get a list of sorted tuples.
my_dict = {'z': 3, 'a': 1, 'c': 2}
sorted_dict = dict(sorted(my_dict.items(), key=lambda item: item[1]))
print(sorted_dict) # {'a': 1, 'c': 2, 'z': 3}
What you learned & what's next
You now know how to sort data with sorted() and custom keys in Python. You can:
- Apply a key function (lambda, operator, custom) to control sorting by any attribute.
- Sort by multiple criteria using a tuple key.
- Reverse order with
reverse=True. - Choose between
sorted()andlist.sort()based on in-place vs. new list. - Handle edge cases like missing keys or wrong types.
This skill is essential for data processing, reporting, and building user-facing features. Next, you'll learn how to combine sorting with filtering using filter() and list comprehensions — powerful for transforming and cleaning datasets.
Practice recap
Write a function sort_students(students) that takes a list of dicts (keys: name, grade, age) and returns a new list sorted by grade descending (numeric), then by name ascending (case-insensitive). Use sorted() with a custom key. Test on [{'name': 'alice', 'grade': 85, 'age': 20}, {'name': 'Bob', 'grade': 92, 'age': 22}].
Common mistakes
- Forgetting that
list.sort()returnsNoneand modifies the original list, whilesorted()returns a new list. Always assign the result ofsorted()to use it. - Using
key=some_dictinstead ofkey=lambda x: some_dict[x]— the key must be a callable, not a static value. - Assuming the key function is only called once — safe, but worrying about performance unnecessarily; Python caches the result per element.
- Sorting dictionaries directly:
sorted(my_dict)sorts only the keys. Use.items()to sort by values.
Variations
- Use
operator.attrgetter('attribute')for sorting objects by attribute instead of lambdas. - Use
functools.cmp_to_key()if you need a comparator-style function (e.g., porting from Python 2). - For natural string sorting (e.g., 'a1', 'a10'), use a key that splits numeric parts — or the
natsortlibrary.
Real-world use cases
- E-commerce: sort products by price, rating, or discount percentage with a custom key on nested dict fields.
- Log analysis: sort log entries by timestamp string using
lambda x: datetime.strptime(x['time'], '%Y-%m-%d %H:%M:%S'). - Leaderboard: sort player scores descending, then by name ascending — use a tuple key with
key=lambda p: (-p['score'], p['name']).
Key takeaways
- The
keyparameter ofsorted()transforms each item before comparison — the original items remain unchanged in the result. - Use a lambda for simple expressions,
operator.itemgetterfor performance, and a named function for reuse. - Sort by multiple criteria with a tuple key — secondary sorting happens automatically on tie.
- Add
reverse=Trueto sort descending, or negate a numeric key for descending while keeping second key ascending. - Always ensure the key function handles all element types — use
.get()with defaults for missing dict keys. sorted()returns a new list;list.sort()sorts in place and returnsNone.
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.