Quick answer: use sorted(iterable) when the input must remain unchanged or may be any iterable; use list.sort() when you intend to modify an existing list. Both accept key for the ordering criterion and reverse=True for descending order. sort() returns None, not the sorted list.
numbers = [8, 2, 10, 3]
ordered = sorted(numbers) # [2, 3, 8, 10]
numbers.sort(reverse=True) # numbers becomes [10, 8, 3, 2]
Sorting becomes less obvious with mixed case, records, missing values, and multiple criteria. This guide builds on Python list manipulation to handle those cases without surprising mutation.
sorted() returns a new list
The built-in sorted() accepts any iterable and always returns a list. It leaves the input unchanged:
names = ("Lia", "Ana", "Chris")
result = sorted(names)
print(result) # ['Ana', 'Chris', 'Lia']
print(names) # ('Lia', 'Ana', 'Chris')
This fits tuples, sets, generators, dictionary keys, and every situation where the original order remains useful. Its official signature is sorted(iterable, /, *, key=None, reverse=False); key and reverse are keyword-only. See the sorted() reference.
Applying sorted() directly to a dictionary sorts its keys. To order key-value pairs, pass dictionary.items(), a pattern related to the guide to Python dictionaries.
stock = {"keyboard": 8, "mouse": 15, "monitor": 4}
by_name = sorted(stock)
by_quantity = sorted(stock.items(), key=lambda item: item[1])
list.sort() changes the list
The list.sort() method orders a list in place and returns None. It avoids a second output list and clearly signals that this collection's order should change.
prices = [39.90, 12.50, 25.00]
returned = prices.sort()
print(prices) # [12.5, 25.0, 39.9]
print(returned) # None
A classic mistake is prices = prices.sort(). Afterward, prices is None. Mutating methods conventionally avoid returning their collection so the side effect cannot be mistaken for a transformation. A practical choice is:
- Use
sorted()to retain the source, support a general iterable, or produce a value in an expression. - Use
.sort()when the input is already a list, mutation is allowed, and its previous order is unnecessary.
Both rely on the same stable sorting algorithm and support key and reverse. The official Python Sorting HOWTO documents the recommended patterns.
key defines the criterion
Without key, Python compares the elements themselves. With key, it calls the function once per element, stores the computed key, and compares those keys. The function should not compare two elements; it transforms one element into its ordering value.
words = ["pear", "pineapple", "fig", "banana"]
by_length = sorted(words, key=len)
## ['fig', 'pear', 'banana', 'pineapple']
Use an existing function instead of a lambda when it already expresses the intent. str.casefold offers broader Unicode case normalization than str.lower:
names = ["Álvaro", "bianca", "Ana", "chris"]
ordered = sorted(names, key=str.casefold)
This still follows Unicode values rather than every linguistic collation convention. Locale-aware display order needs an appropriate collation solution. Lowercasing or stripping accents is not automatically equivalent to a user's language rules.
Dictionaries, tuples, and objects
A short lambda makes a record field explicit:
products = [
{"name": "Mouse", "price": 90.0},
{"name": "Keyboard", "price": 180.0},
{"name": "Cable", "price": 25.0},
]
by_price = sorted(products, key=lambda product: product["price"])
For repeated field access, operator.itemgetter() and operator.attrgetter() are concise alternatives covered by the operator module documentation:
from operator import attrgetter, itemgetter
by_name = sorted(products, key=itemgetter("name"))
class Order:
def __init__(self, customer, total):
self.customer = customer
self.total = total
orders = [Order("Beth", 80), Order("Chris", 45)]
by_total = sorted(orders, key=attrgetter("total"))
Objects without a natural ordering need not implement comparison operators when a call supplies a key. This keeps a presentation-specific rule outside the model. The guide to object-oriented Python provides more background on such classes.
Multiple criteria with tuple keys
Tuples compare item by item, making a tuple key the direct solution for multiple fields:
students = [
{"class": "B", "grade": 9, "name": "Ana"},
{"class": "A", "grade": 8, "name": "Chris"},
{"class": "A", "grade": 9, "name": "Beth"},
]
result = sorted(
students,
key=lambda student: (
student["class"],
-student["grade"],
student["name"].casefold(),
),
)
This groups by ascending class, places higher grades first, and breaks ties by name. Negating a number is convenient for mixed directions but applies only to types supporting negation. For complex rules, use stable passes from the secondary criterion to the primary one:
result = sorted(students, key=lambda student: student["name"].casefold())
result.sort(key=lambda student: student["grade"], reverse=True)
result.sort(key=lambda student: student["class"])
Stability and reverse
Python sorting is stable: when two keys compare equal, their elements retain the relative order they had in the input. This ensures predictable ties and enables the multi-pass pattern above.
reverse=True creates descending order while preserving stability:
sales = [("Ana", 50), ("Beth", 80), ("Chris", 50)]
largest_first = sorted(sales, key=lambda sale: sale[1], reverse=True)
## Ana remains before Chris among equal values of 50.
Avoid reversed(sorted(...)) unless an iterator is specifically needed. It reverses the relative position of ties too, while reverse=True communicates and implements descending stable sorting directly.
Missing data and incompatible types
Python 3 does not define ordering between arbitrary unlike values such as None and int, or str and float. Normalize data in the key:
records = [
{"name": "Ana", "age": 30},
{"name": "Beth", "age": None},
{"name": "Chris", "age": 22},
]
by_age = sorted(
records,
key=lambda record: (
record["age"] is None,
record["age"] if record["age"] is not None else 0,
),
)
The first tuple field puts present values before None; the second orders actual ages. Do not silently treat missing data as zero when their meanings differ. A composite key preserves the distinction.
Performance and common mistakes
Python uses Timsort, designed to benefit from ordered runs already present in real data. Broadly, sorting takes O(n log n) time in typical cases and additional memory for keys and internal organization. Measure before optimizing.
Avoid expensive work in old-style custom comparators. A key is evaluated only once per element, one major reason it is preferred. To transform or filter a collection before ordering, compare list comprehensions with lambda, map, and filter.
Frequent errors include assigning .sort()'s result, forgetting that strings are case-sensitive, sorting numeric strings ("10" precedes "2"), mixing incomparable types, and returning inconsistent types from a key. Convert values at the data boundary and keep the key small, deterministic, and free of side effects.
Final checklist
- Preserve input with
sorted(); mutate a list with.sort(). - Never assign the return value of
.sort(). - Make
keyaccept one element and return a comparable criterion. - Use tuples for explicit criteria and tie-breakers.
- Use
reverse=Truefor stable descending order. - Normalize case, numeric strings, and missing values deliberately.
- Prefer
itemgetterorattrgetterwhen they improve readability.
Good sorting code makes the business rule visible. First decide whether the original collection may change, then write a key that represents the desired criterion exactly. Stability and tuple keys keep even sophisticated ordering concise and testable.