Sorting lists in Python comes down to two questions: do you want the original list reordered, and what are you comparing? The first is answered by sort() versus sorted(), the second by the key argument — and getting either wrong produces a bug that is easy to misread.
What is the difference between sort() and sorted()?
list.sort() is a method: it rearranges the list in place and leaves you with the same object, reordered. sorted() is a function: it takes any iterable and gives you back a new sorted list, leaving the original alone.
scores = [3, 1, 2]
scores.sort()
print("after sort:", scores)
original = [3, 1, 2]
ordered = sorted(original)
print("ordered:", ordered)
print("original:", original)
That difference matters more than it first appears. If two parts of your program hold references to the same list, calling sort() changes what both of them see. If you sort a list you received as an argument, you have modified your caller's data as a side effect that is invisible in the function's signature.
sorted() also accepts things that are not lists: a tuple, a set, a generator, or a dictionary, in which case it sorts the keys. It always returns a list.
print(sorted((3, 1, 2)))
print(sorted({"b", "a", "c"}))
print(sorted({"b": 2, "a": 1}))
Sorting a set is worth a second thought. The set itself has no meaningful order, so sorted() is the only way to get a predictable sequence out of one — but the order you get is a consequence of the values, not of any order you chose. If you need the order you inserted them in, keep a list.
Why does sorting a list return None?
Because sort() modifies the list and has nothing to hand back. This produces one of the most reliable beginner bugs in Python, because the mistake looks like it should work.
scores = [3, 1, 2]
scores = scores.sort()
print("scores is now:", scores)
try:
print(len(scores))
except TypeError as exc:
print("TypeError:", exc)
After the assignment, scores is None, because the right-hand side evaluated to None and that is what got assigned. The original list still exists somewhere, but you have thrown away the only name that pointed at it.
Two habits prevent this. Write scores.sort() as a statement and never assign its result. Or, if you were reaching for a new list anyway, use sorted(scores), which returns the list you were expecting. When in doubt, prefer sorted: the in-place variant buys you a little memory and nothing else.
There is a related trap with the built-in methods that modify in place. list.append, list.extend, list.sort, list.reverse and list.insert all return None, because their job is the change, not the result. Chained calls like items.append(1).sort() therefore fail on the first link, with an error message that points at None rather than at the chain.
How do you sort by a key?
By default, Python compares the elements themselves, which works when they are all the same kind of thing. To sort by something else, pass key= a function. Python calls it once per element and compares the results instead of the elements.
words = ["Pee", "wee", "Bee"]
print(sorted(words))
print(sorted(words, key=str.lower))
print(sorted(["bb", "a", "ccc"], key=len))
Three useful keys cover most cases: str.lower for case-insensitive text, len for length, and a lambda for anything you compute yourself. The lambda is the flexible one, and it is how you sort a list of dictionaries or objects by an attribute.
A special case of that is data that arrives as text but should be ordered as numbers. Sorting the strings directly puts "100" before "9", because the comparison happens character by character. A key of int converts each value first and gives you the order a human expects.
numbers = ["10", "9", "100"]
print(sorted(numbers))
print(sorted(numbers, key=int))
people = [
{"name": "Ada", "age": 36},
{"name": "Grace", "age": 45},
{"name": "Alan", "age": 41},
]
by_age = sorted(people, key=lambda p: p["age"])
print([p["name"] for p in by_age])
oldest_first = sorted(people, key=lambda p: p["age"], reverse=True)
print([p["name"] for p in oldest_first])
reverse=True flips the final order. It does not change how the key is computed, so sorting by name descending still compares names — it just puts Z before A. Note that reverse=True is not the same as reversed(list): the first sorts and flips, the second only flips.
If your key is a single field, operator.itemgetter is a leaner alternative to the lambda and it reads well when the field name is the point of the sort.
from operator import itemgetter
staff = [{"name": "Ada", "age": 36}, {"name": "Grace", "age": 45}, {"name": "Alan", "age": 41}]
print(sorted(staff, key=itemgetter("age"))[0]["name"])
print(sorted([("b", 2), ("a", 2), ("c", 1)], key=itemgetter(1)))
The number in itemgetter(1) means "the second element of each tuple", because indexing starts at zero. It is the same key function as lambda pair: pair[1], written without the function-call overhead.
How do you sort by more than one field?
Return a tuple from the key function. Python compares tuples element by element, so the first element becomes the primary sort key and the rest act as tie-breakers.
staff = [
{"name": "Ada", "age": 36, "city": "London"},
{"name": "Grace", "age": 45, "city": "New York"},
{"name": "Alan", "age": 41, "city": "London"},
]
ordered = sorted(staff, key=lambda p: (p["city"], p["age"]))
print([(p["city"], p["name"]) for p in ordered])
The example sorts by city first and by age within each city. Swap the tuple elements and the priority swaps with them. If you want a descending sort on the second field only, that is not something the tuple trick does directly, and you should sort by the first key, reverse, then sort stably by the second — Python's sort is stable, so equal keys keep their existing relative order, which makes that two-step technique reliable.
What happens when you sort mixed types?
You get a TypeError. Python cannot decide whether 3 comes before "two", so it refuses rather than guessing.
mixed = [1, "two", 3.0]
try:
sorted(mixed)
except TypeError as exc:
print("TypeError:", exc)
Sorting a dictionary is not a special case — you sort its keys. When you want the entries sorted by value, sort the items and give a key that reaches the second element of each pair.
scores = {"Ada": 36, "Grace": 45, "Alan": 41}
print(sorted(scores))
print(sorted(scores.items()))
print(sorted(scores.items(), key=lambda pair: pair[1]))
One more property is worth knowing because it makes multi-key sorting predictable: Python's sort is stable. Elements whose keys compare equal keep the order they had before the sort, so sorting by one key and then by another never scrambles the first result.
rows = [("a", 2), ("b", 1), ("c", 2), ("d", 1)]
print(sorted(rows, key=lambda r: r[1]))
Sorting cheat sheet
| Goal | Code | Original list |
|---|---|---|
| Sort in place, ascending | lst.sort() | reordered |
| New sorted list | sorted(lst) | untouched |
| Descending | sorted(lst, reverse=True) | untouched |
| Sort by a field | sorted(lst, key=lambda x: x["field"]) | untouched |
| Case-insensitive | sorted(lst, key=str.lower) | untouched |
| Two keys | sorted(lst, key=lambda x: (x["a"], x["b"])) | untouched |
Try it yourself: sort a list of ten names case-insensitively and print both the sorted list and the untouched original. Then sort a list of dictionaries by two fields and reverse the primary one. Next, deliberately write x = x.sort() on a scratch list, catch the resulting TypeError, and replace it with the correct call. Finally, sort a dictionary's items by value and print the keys in that order.