You called list.sort(), assigned the result to a variable, then printed it and got None. Here's what happened and how to avoid it.

  1. list.sort() sorts in place and returns None. Never assign its result.
  2. sorted() returns a new list and leaves the original untouched.
  3. Both accept key= and reverse=True parameters.
  4. Python's sort is stable: equal elements keep their original relative order.

The Most Common Mistake: sort() Returns None

# Wrong
numbers = [3, 1, 4, 1, 5]
sorted_numbers = numbers.sort()  # sorted_numbers is None
print(sorted_numbers)  # None
# Correct
numbers.sort()
print(numbers)  # [1, 1, 3, 4, 5]
# Or use sorted() if you need a new list
sorted_numbers = sorted(numbers)

list.sort() modifies the original list and returns None by design. If you need the sorted result as a value, use sorted() instead.

list.sort() vs sorted(): When to Use Which

Both functions use Timsort under the hood (O(n log n) worst case) and accept identical key= and reverse= parameters. The difference is about mutability and flexibility.

list.sort()sorted()
Return valueNoneNew sorted list
Modifies originalYesNo
Works onLists onlyAny iterable
MemoryMore efficientCreates a copy

Use list.sort() when you no longer need the original order and want to save memory. Use sorted() when you need to keep the original, or when working with non-list iterables like tuples, sets, or generator expressions.

# sorted() works on any iterable
print(sorted((3, 1, 2)))        # tuple: [1, 2, 3]
print(sorted({3, 1, 2}))        # set: [1, 2, 3]
print(sorted("cba"))            # string: ['a', 'b', 'c']
# list.sort() is list-only
(3, 1, 2).sort()  # AttributeError: 'tuple' object has no attribute 'sort'

Sorting with the key Parameter

The key parameter accepts any callable. Python calls it once per element and sorts by the returned values, not the elements themselves.

words = ["banana", "apple", "kiwi", "cherry"]
# Sort by string length
words.sort(key=len)
print(words)  # ['kiwi', 'apple', 'banana', 'cherry']
# Case-insensitive sort
names = ["Charlie", "alice", "Bob"]
sorted_names = sorted(names, key=str.lower)
print(sorted_names)  # ['alice', 'Bob', 'Charlie']
# Sort by last character
sorted(words, key=lambda x: x[-1])

Sorting Lists of Dictionaries

This is the most common real-world use case. Sort a list of dicts by any field:

students = [
    {"name": "Alice", "score": 88},
    {"name": "Bob", "score": 95},
    {"name": "Charlie", "score": 72},
]
# Sort by score ascending
by_score = sorted(students, key=lambda x: x["score"])
# Sort by score descending
by_score_desc = sorted(students, key=lambda x: x["score"], reverse=True)
# Sort by name alphabetically
by_name = sorted(students, key=lambda x: x["name"])

For simple field access, operator.itemgetter is slightly faster than a lambda and more readable:

from operator import itemgetter
sorted(students, key=itemgetter("score"))
sorted(students, key=itemgetter("score", "name"))  # multi-field

Sorting Custom Objects

Use lambda or operator.attrgetter to sort objects by their attributes:

from operator import attrgetter
class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
students = [Student("Alice", 88), Student("Bob", 95), Student("Charlie", 72)]
# Sort by score
students.sort(key=attrgetter("score"))
# Sort by multiple attributes: score desc, then name asc
students.sort(key=attrgetter("name"))   # secondary sort first
students.sort(key=attrgetter("score"), reverse=True)  # primary sort second

That last pattern relies on sort stability: when the primary sort runs, equal elements keep the order established by the secondary sort. Python guarantees this.

Multi-Criteria Sorting in One Pass

The cleaner way is to return a tuple from the key function. Python compares tuples element-by-element:

students = [
    {"name": "Alice", "grade": "A", "score": 95},
    {"name": "Bob", "grade": "A", "score": 88},
    {"name": "Charlie", "grade": "B", "score": 95},
]
# Sort by grade ascending, then score descending
# Negate score to reverse just that field
result = sorted(students, key=lambda x: (x["grade"], -x["score"]))

("A", -95) comes before ("A", -88) because -95 < -88. This trick only works with numeric fields; for strings you need the two-pass approach above.

Handling Missing Keys

When sorting dicts where some entries might not have the field you're sorting on, use .get() with a default:

data = [
    {"name": "Alice", "score": 90},
    {"name": "Bob"},  # no score
    {"name": "Charlie", "score": 75},
]
# float("inf") ensures missing scores sort to the end
# Without a fallback, x["score"] raises KeyError on the Bob entry
sorted(data, key=lambda x: x.get("score", float("inf")))
# Result: Alice (90), Charlie (75) sorted ascending, Bob last

Without the .get() fallback, you'd get a KeyError on the entries missing the field.

Sort Stability

Python's sort is guaranteed stable. When two elements have equal keys, they keep the same relative order they had before sorting. This matters when you sort by one field and want ties broken by the original order.

data = [("Alice", 2), ("Bob", 1), ("Charlie", 2), ("Dave", 1)]
# Sort by score only
sorted(data, key=lambda x: x[1])
# [('Bob', 1), ('Dave', 1), ('Alice', 2), ('Charlie', 2)]
# Bob comes before Dave (original order), Alice before Charlie (original order)

This stability guarantee makes the two-pass sort pattern reliable and is why you can safely do secondary sorts without corrupting the primary sort order.

Reverse Sorting

Both functions accept reverse=True:

numbers = [3, 1, 4, 1, 5, 9]
numbers.sort(reverse=True)
print(numbers)  # [9, 5, 4, 3, 1, 1]
desc = sorted(numbers, reverse=True)

When using multi-criteria sorting with tuples, reverse=True flips all fields. Negate numeric fields individually if you only want to reverse one of them.

Performance: When It Actually Matters

For most code, the choice between sort() and sorted() is a style decision. The performance difference is meaningful only at scale:

  • list.sort() avoids allocating a new list, which matters when sorting large lists frequently in a tight loop.
  • Timsort is optimized for partially sorted data: if your list is already mostly sorted, it runs significantly faster than O(n log n).
  • If you only need the top N items, heapq.nlargest(n, iterable, key=...) is faster than a full sort followed by slicing.
import heapq
scores = [88, 95, 72, 91, 83]
top3 = heapq.nlargest(3, scores)  # [95, 91, 88], faster than sorted()[-3:]

If you're working in a Python environment and need to check your version first, checking your Python version takes one command. The sorting API shown here works identically from Python 3.0 onward.

For data-heavy Python work, managing your environment with conda environments keeps dependencies isolated and makes it easier to test across Python versions without conflicts.