Python sorted() Function: Syntax, Key, and Behavior
python sorted function: Learn how Python's sorted() function works: its syntax, key and reverse parameters, stability guarantees, and when to use it instead of list.so...
The python sorted function returns a new list containing the elements of any iterable in sorted order. Unlike list.sort(), which sorts the list in place and returns None, sorted() works on any iterable—lists, tuples, strings, dictionaries, sets, and generators—and leaves the original object untouched.
The sorted() Function Signature
sorted(iterable, *, key=None, reverse=False)
The iterable argument is the only required parameter. The key and reverse parameters are keyword-only, meaning you cannot pass them positionally. This design choice prevents accidental misuse and keeps the call site explicit about which optional behavior is being requested.
numbers = [3, 1, 4, 1, 5, 9, 2, 6] result = sorted(numbers) print(result) # [1, 1, 2, 3, 4, 5, 6, 9] print(numbers) # [3, 1, 4, 1, 5, 9, 2, 6] — unchanged
The original list remains intact. This makes sorted() the safer choice when the ordering operation is part of a larger computation and the source data must be preserved for subsequent processing.
Using the key Parameter for Custom Sort Order
The key parameter accepts a callable that transforms each element before comparison. The function is applied exactly once per element, and the transformed values are used for ordering. The original elements are returned in the result.
words = ["banana", "apple", "Cherry", "date"] result = sorted(words, key=str.lower) print(result) # ['apple', 'banana', 'Cherry', 'date']
Without key=str.lower, the sort uses default lexicographic comparison, which places uppercase letters before lowercase ones because their Unicode code points are smaller. The result would be ['Cherry', 'apple', 'banana', 'date'], which is rarely what a developer expects when sorting user-facing strings.
The key function can be a lambda, a built-in function, a method, or any callable object:
data = [("Alice", 34), ("Bob", 29), ("Carol", 31)] by_age = sorted(data, key=lambda person: person[1]) print(by_age) # [('Bob', 29), ('Carol', 31), ('Alice', 34)]
For attribute access on objects, operator.attrgetter is more readable than a lambda and avoids the extra function call layer:
from operator import attrgetter class Employee: def __init__(self, name, salary): self.name = name self.salary = salary employees = [ Employee("Alice", 95000), Employee("Bob", 82000), Employee("Carol", 110000), ] by_salary = sorted(employees, key=attrgetter("salary"))
Sorting Dictionaries and Other Iterables
sorted() works directly on dictionary keys:
scores = {"alice": 87, "bob": 92, "carol": 78} print(sorted(scores)) # ['alice', 'bob', 'carol']
To sort by values, pass scores.get as the key function:
sorted(scores, key=scores.get) # ['carol', 'alice', 'bob']
For dictionaries, sorted() iterates over keys by default. To sort key-value pairs, iterate over .items() and use a key function that selects the value:
sorted(scores.items(), key=lambda item: item[1])
This returns a list of tuples. If you need a dictionary back, wrap it in dict()—which is reliable in Python 3.7+ where dictionary insertion order is guaranteed by the language specification.
sorted() vs list.sort(): When to Use Each
| Criterion | sorted() | list.sort() |
|---|---|---|
| Return value | New list | None (in-place) |
| Works on | Any iterable | Lists only |
| Original data | Unchanged | Modified |
| Memory | Creates a copy | No copy needed |
Use list.sort() when you own the list, no other code holds a reference that depends on the original order, and you want to avoid allocating a second list. Use sorted() when the data is a tuple, generator, or dictionary, or when the original order must be preserved for later use.
Stability and Runtime Behavior
Python's sort is stable: elements that compare equal retain their original relative order. This matters when sorting by multiple criteria in successive passes:
records = [ ("Alice", "engineering"), ("Bob", "sales"), ("Carol", "engineering"), ] records.sort(key=lambda r: r[1]) # sort by department first records.sort(key=lambda r: r[0]) # then by name
Because the second sort is stable, records with the same name remain ordered by department from the first pass. A single sort with a tuple key achieves the same result more directly:
sorted(records, key=lambda r: (r[0], r[1]))
The underlying algorithm is Timsort, a hybrid stable sort with O(n log n) worst-case complexity. Timsort exploits existing runs of ordered data, so sorting an already-sorted or partially-sorted sequence is significantly faster than sorting random data. This is a practical reason to avoid re-sorting data that is already ordered.
Common Edge Cases and Mistakes
Mixed-type comparisons raise TypeError:
sorted([1, "two", 3]) # TypeError: '<' not supported between instances of 'str' and 'int'
This is intentional. Python does not define a meaningful ordering between arbitrary types. If you must sort heterogeneous data, provide an explicit key that maps every element to a comparable value.
The reverse parameter reverses the final order, not the key comparison:
sorted([1, 3, 2], reverse=True) # [3, 2, 1]
If you need descending order on one field and ascending on another, use a negative numeric key or sort in multiple passes:
sorted(records, key=lambda r: (-r[1], r[0]))
Memory and Performance Considerations
sorted() always creates a new list, which means it allocates memory proportional to the input size. For very large sequences, this temporarily doubles the memory footprint during the operation. list.sort() avoids that allocation but destroys the original order.
The key function is called once per element, and the results are cached internally. This means an expensive key function—such as one that parses a date string or performs a lookup—is evaluated exactly n times, not O(n log n) times. Reusing the same key function across repeated sorts of the same data is still wasteful; cache the transformed values yourself if the data does not change between sorts.
For large datasets where memory is a constraint, consider whether list.sort() on a copy is acceptable, or whether the data can be pre-processed to reduce the cost of the key computation. Sorting a generator with sorted() materializes the entire sequence into memory, so streaming the data through a different ordering strategy may be necessary when the input is too large to fit comfortably in memory.