Maintenance

Site is under maintenance — quizzes are still available.

Go to quizzes

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

Sponsored

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 key function 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. The key parameter specifies a function of one argument that is used to extract a comparison key from each element.
  • key function: Runs once per element during sorting. Its return value is used for comparison, not the raw element. Common key functions include len, str.lower, itemgetter, attrgetter, or lambda functions.
  • reverse=True: If you need descending order, pass reverse=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):

  1. 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')].
  2. 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')].
  3. 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

  1. Identify the comparison value you need to extract from each element.
  2. Write a function that takes one element and returns that comparison value.
  3. Pass that function as the key argument to sorted().
# 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

  1. Forgetting that list.sort() returns None python my_list = [3, 1, 2] result = my_list.sort() # result is None! print(result) # None

  2. 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

  3. 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 key parameter accepts any callable (function, lambda, operator module) to extract a comparison value.
  • Tuples as keys enable multi-level sorting with tie-breakers.
  • reverse=True flips 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() sets result to None, 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.lower or str.casefold for case-insensitive sorts.

Variations

  1. Use sorted(..., reverse=True) for descending order instead of manually negating numeric keys.
  2. Use functools.cmp_to_key() if you need to convert an old-style comparison function (from Python 2) to a key function.
  3. For very large datasets, consider numpy.sort for numeric arrays or pandas.DataFrame.sort_values for 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; use list.sort() for in-place sorting.
  • The key parameter 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=True or negate numeric keys in the tuple.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.