Sorting with sorted() & key

Reviewed & published by Brayan K

Sorting in Python means ordering items with sorted() (which returns a new list) or list.sort() (which sorts in place), optionally guided by a key function that decides what to sort by.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Master the key argument and you can order anything — words by length, records by a field, or by several fields at once — using Python's fast, stable sort.

What You'll Learn in This Lesson

1 sorted() vs list.sort() vs reverse=

sorted() hands back a new list and leaves the original alone; .sort() rearranges the list in place and returns None:

nums = [3, 1, 4, 1, 5, 9, 2]

# sorted() -> new list, original untouched
new_list = sorted(nums)
print(new_list)          # [1, 1, 2, 3, 4, 5, 9]
print(nums)              # [3, 1, 4, 1, 5, 9, 2]  (unchanged)

# .sort() -> sorts in place, returns None
nums.sort()
print(nums)              # [1, 1, 2, 3, 4, 5, 9]

# reverse=True for descending order
print(sorted([3, 1, 2], reverse=True))   # [3, 2, 1]

# sorted() works on ANY iterable (sort() is list-only)
print(sorted("dcba"))    # ['a', 'b', 'c', 'd']
# sorted() vs .sort() and reverse=
words = ["pear", "apple", "fig", "cherry"]

# sorted() returns a new list; original stays put
alpha = sorted(words)
print("Sorted:", alpha)        # ['apple', 'cherry', 'fig', 'pear']
print("Original:", words)      # ['pear', 'apple', 'fig', 'cherry']

# Descending with reverse=True
print(sorted([5, 2, 8, 1], reverse=True))   # [8, 5, 2, 1]

# .sort() mutates in place and returns None
nums = [4, 2, 6, 1]
result = nums.sort()
print("Returned:", result)     # Returned: None
print("Now nums:", nums)       # Now nums: [1, 2, 4, 6]

# ✅ Expected output:
# Sorted: ['apple', 'cherry', 'fig', 'pear']
# Original: ['pear', 'apple', 'fig', 'cherry']
# [8, 5, 2, 1]
# Returned: None
# Now nums: [1, 2, 4, 6]

🔑 The key= Function

The key function tells Python what to sort by. It's called once per item, and the items are ordered by those computed values:

words = ["banana", "kiwi", "apple", "fig"]

# Sort by length, not alphabetically
print(sorted(words, key=len))
# ['fig', 'kiwi', 'apple', 'banana']

# Case-insensitive sort
names = ["bob", "Alice", "charlie", "Dave"]
print(sorted(names, key=str.lower))
# ['Alice', 'bob', 'charlie', 'Dave']

# A lambda for anything custom: sort by last character
print(sorted(words, key=lambda w: w[-1]))
# ['banana', 'apple', 'kiwi', 'fig']

# Sort dicts by a field
people = [{"name": "Ada", "age": 36}, {"name": "Ben", "age": 24}]
print(sorted(people, key=lambda p: p["age"]))
# [{'name': 'Ben', 'age': 24}, {'name': 'Ada', 'age': 36}]

The elements themselves are never modified — key only computes the value to compare by. The returned list still holds your original items, just reordered.

# Custom ordering with key=
files = ["report.pdf", "a.txt", "summary.docx", "x.md"]

# Sort by filename length
print(sorted(files, key=len))
# ['x.md', 'a.txt', 'report.pdf', 'summary.docx']

# Sort numbers by distance from 10
nums = [3, 14, 8, 21, 9]
print(sorted(nums, key=lambda n: abs(n - 10)))
# [9, 8, 14, 3, 21]

# Sort records by a field, descending
scores = [("Ada", 88), ("Ben", 95), ("Cleo", 72)]
print(sorted(scores, key=lambda s: s[1], reverse=True))
# [('Ben', 95), ('Ada', 88), ('Cleo', 72)]

# ✅ Expected output:
# ['x.md', 'a.txt', 'report.pdf', 'summary.docx']
# [9, 8, 14, 3, 21]
# [('Ben', 95), ('Ada', 88), ('Cleo', 72)]

2 itemgetter, attrgetter & Multi-Key Sorts

The operator module gives clean, fast key functions, and returning a tuple sorts by multiple fields at once:

from operator import itemgetter, attrgetter

people = [
    {"last": "Smith", "first": "Ann", "age": 30},
    {"last": "Smith", "first": "Bob", "age": 25},
    {"last": "Adams", "first": "Zoe", "age": 40},
]

# itemgetter pulls dict keys (or list indices)
by_age = sorted(people, key=itemgetter("age"))
print([p["first"] for p in by_age])   # ['Bob', 'Ann', 'Zoe']

# Multi-key: sort by last name, then first name
by_name = sorted(people, key=itemgetter("last", "first"))
print([(p["last"], p["first"]) for p in by_name])
# [('Adams', 'Zoe'), ('Smith', 'Ann'), ('Smith', 'Bob')]
from dataclasses import dataclass
from operator import attrgetter

@dataclass
class Player:
    name: str
    score: int

players = [Player("Ada", 90), Player("Ben", 90), Player("Cleo", 75)]

# attrgetter pulls object attributes
print([p.name for p in sorted(players, key=attrgetter("name"))])
# ['Ada', 'Ben', 'Cleo']

# Tuple key: highest score first, then name A-Z (negate score to flip)
ranked = sorted(players, key=lambda p: (-p.score, p.name))
print([(p.name, p.score) for p in ranked])
# [('Ada', 90), ('Ben', 90), ('Cleo', 75)]

⚖️ Stable Sort & Multi-Pass Sorting

Python's sort is stable: items that compare equal keep their original order. That lets you sort in stages — minor key first, then major key:

data = [
    ("Ada", "B"),
    ("Ben", "A"),
    ("Cleo", "B"),
    ("Dan", "A"),
]

# Pass 1: sort by name (the minor key)
step1 = sorted(data, key=lambda x: x[0])
# Pass 2: sort by grade (the major key) - ties keep name order
step2 = sorted(step1, key=lambda x: x[1])

print(step2)
# [('Ben', 'A'), ('Dan', 'A'), ('Ada', 'B'), ('Cleo', 'B')]
# Within each grade, names stay alphabetical thanks to stability

Because equal elements never get shuffled, a second sort only reorders what the first one tied on. This staged approach is often clearer than one giant tuple key.

🎯 Your Turn: Fill in the Blanks

Replace each ___ to complete the sorts so the output matches.

# 🎯 YOUR TURN: replace each ___ to match the expected output
words = ["kiwi", "fig", "banana", "pear"]

# 1. Sort by length -> ['fig', 'kiwi', 'pear', 'banana']
print(sorted(words, ___))

# 2. Sort numbers descending -> [9, 5, 3, 1]
print(sorted([3, 9, 1, 5], ___))

# 3. Sort people by age, then name on ties
people = [("Ada", 30), ("Ben", 25), ("Cleo", 25)]
print(sorted(people, key=lambda p: (___)))
# -> [('Ben', 25), ('Cleo', 25), ('Ada', 30)]

# Expected output:
# ['fig', 'kiwi', 'pear', 'banana']
# [9, 5, 3, 1]
# [('Ben', 25), ('Cleo', 25), ('Ada', 30)]
print(sorted(words, key=len))
print(sorted([3, 9, 1, 5], reverse=True))
print(sorted(people, key=lambda p: (p[1], p[0])))   # age, then name

Common Errors & How to Fix Them

❌ Assigning the result of .sort()

nums = nums.sort()   # nums is now None!

✅ .sort() returns None. Call it on its own line, or use nums = sorted(nums).

❌ Calling the key function yourself

sorted(words, key=len(words))   # TypeError - pass the function, not a call

✅ Pass the function object: key=len, not key=len(...). Python calls it for each item.

❌ Sorting mixed, uncomparable types

sorted([3, "a", 1])   # TypeError: '<' not supported between int and str

✅ Give a key that maps everything to a comparable type, e.g. key=str, or clean the data first.

🎯 Mini Challenge: Leaderboard

Build a leaderboard: highest score first, ties broken alphabetically by name.

# Mini Challenge: rank players by score desc, then name asc
players = [
    {"name": "Cleo", "score": 88},
    {"name": "Ada", "score": 95},
    {"name": "Ben", "score": 88},
    {"name": "Dan", "score": 72},
]

# Tuple key: -score for descending, name for the tie-break
ranked = sorted(players, key=lambda p: (-p["score"], p["name"]))

for rank, p in enumerate(ranked, start=1):
    print(f"{rank}. {p['name']} - {p['score']}")

# Expected output:
# 1. Ada - 95
# 2. Ben - 88
# 3. Cleo - 88
# 4. Dan - 72

📚 Keep Learning

Go deeper with the official Python documentation:

Frequently Asked Questions

Lesson complete — you can order anything!

You know sorted() returns a new list while .sort() mutates in place, you can reverse, sort by a key (lambda, itemgetter, attrgetter), break ties with a tuple key, and lean on Python's stable sort for multi-pass ordering.

Practice quiz

What does sorted() return?

  • None, after sorting in place
  • The original list, now sorted
  • A brand-new sorted list, leaving the original untouched
  • A sorted tuple

Answer: A brand-new sorted list, leaving the original untouched. sorted() returns a new sorted list and does not modify the original iterable.

What does list.sort() return?

  • None
  • A new sorted list
  • The sorted list
  • The number of items sorted

Answer: None. .sort() sorts the list in place and returns None — never write x = x.sort().

What does sorted([3, 1, 2], reverse=True) produce?

  • [1, 2, 3]
  • [2, 1, 3]
  • None
  • [3, 2, 1]

Answer: [3, 2, 1]. reverse=True sorts in descending order, giving [3, 2, 1].

What is the role of the key argument?

  • It filters out elements that don't match
  • It is a function applied to each element to decide what to sort BY
  • It sets the sort algorithm
  • It reverses the order

Answer: It is a function applied to each element to decide what to sort BY. key=f computes f(item) for each element and sorts by those values; the elements themselves are unchanged.

What does sorted(['banana','kiwi','apple','fig'], key=len) return?

  • ['fig','kiwi','apple','banana']
  • ['apple','banana','fig','kiwi']
  • ['banana','apple','kiwi','fig']
  • ['kiwi','fig','apple','banana']

Answer: ['fig','kiwi','apple','banana']. Sorting by len orders by string length: fig(3), kiwi(4), apple(5), banana(6).

How do you sort by multiple fields at once?

  • Call sorted() twice with the same key
  • Pass reverse=True
  • Return a tuple from the key function
  • Use key=str only

Answer: Return a tuple from the key function. Returning a tuple like (p.last, p.first) makes Python compare element by element — primary then tie-breaker.

What does it mean that Python's sort is 'stable'?

  • It never crashes
  • Equal elements keep their original relative order
  • It always uses the same amount of memory
  • It sorts numbers before strings

Answer: Equal elements keep their original relative order. Stability means items that compare equal stay in their original order, which enables multi-pass sorting.

To rank by score descending then name ascending, which key works?

  • key=lambda p: (p.score, p.name)
  • key=lambda p: (p.name, p.score), reverse=True
  • key=lambda p: p.score, p.name
  • key=lambda p: (-p.score, p.name)

Answer: key=lambda p: (-p.score, p.name). Negating the numeric score flips just that field to descending while name stays ascending.

Why does sorted([3, 'a', 1]) raise a TypeError?

  • The list is too short
  • '<' is not supported between instances of int and str
  • sorted() needs a key argument
  • Lists cannot contain strings

Answer: '<' is not supported between instances of int and str. Python can't compare int and str directly. Provide a key that maps everything to a comparable type.

Which is true about sorted() vs .sort() regarding inputs?

  • Both work on any iterable
  • .sort() works on any iterable; sorted() is list-only
  • sorted() works on any iterable; .sort() is a list-only method
  • Neither works on strings

Answer: sorted() works on any iterable; .sort() is a list-only method. sorted() accepts any iterable (e.g. a string) and returns a list; .sort() exists only on lists.

Continue this course

Frequently asked questions

What's the difference between sorted() and list.sort()?

sorted() is a built-in function that takes any iterable and RETURNS a brand-new sorted list, leaving the original untouched. list.sort() is a method that sorts a list IN PLACE and returns None. Use sorted() when you want a new list or you're sorting something that isn't a list; use .sort() when you own the list and don't need the original order.

What does the key argument do?

key is a function applied to each element to decide what to sort BY. Python calls key(item) for every element and sorts by those computed values, not the elements themselves. So key=len sorts strings by length, key=str.lower sorts case-insensitively, and key=lambda p: p['age'] sorts dicts by their age field. The elements themselves are never changed.

How do I sort by more than one field?

Return a tuple from your key function. Python compares tuples element by element, so key=lambda p: (p.last, p.first) sorts by last name, then breaks ties by first name. To mix directions, sort by the secondary key first, or negate a numeric field like key=lambda p: (-p.score, p.name) for highest score first then name A-Z.

What does 'stable sort' mean and why does it matter?

A stable sort keeps equal elements in their original relative order. Python's sort is guaranteed stable, which lets you sort in stages: sort by a minor key first, then by a major key, and items that tie on the major key stay in the minor-key order. It's the reason multi-pass sorting works reliably.