LESSON 28 · DEEPER PYTHON26 min

Sort Python Data with key and reverse

Choose a sort key, control direction, and preserve equal-key order without changing the original data.

Before you start: Lists · Dictionaries · Functions

Video lesson: Sort Python Data with key and reverse

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    We want an alphabetic list of names. Most people looking at Zoe, ada, and Bo would expect ada, Bo, Zoe. Python can sort the strings, but its default comparison does not treat uppercase and lowercase letters as the same letter. That difference is easy to miss when a short test list happens to contain names with the same capitalization. In this video we will run the default sort, inspect the surprise, and tell Python which comparison value to use. We will also distinguish sorted from list.sort, handle duplicate comparison values, and test that the original list stays in place when we want a new result.

  2. 00:40

    The companion lesson gives you a browser console and a checked exercise. Before running each version, predict what it should print. A correct sort depends on the rule we choose. Alphabetical by name, shortest name first, and highest score first are three different rules for the same input. We should make the chosen rule visible in code and in tests. Keep the original spelling in the result even if we compare a normalized form. The output is a valid default string sort, but it is not the human-facing order we wanted. Python compares the strings according to their characters, and uppercase B and Z sort before lowercase a here.

  3. 01:21

    Notice a second fact: sorted returned a new list. The original names list still has the same order. That matters when another part of a program uses the input order later. If our task is to display a sorted view, changing the source list would be an unnecessary side effect. The wrong output is useful evidence. It tells us the algorithm sorted something consistently, but the comparison rule was wrong for the product requirement. A tempting patch is to lowercase every name in the input list before sorting. That would lose the capitalization we want to display. We only need a lowercase-like comparison value, not a rewritten name.

  4. 02:03

    This is exactly what the key parameter provides. It calls a function once for each item to get the value used for comparison. The resulting list still contains the original items, with their original spelling. Keep that distinction in mind before the fix. Each original name has a comparison key. Zoe becomes zoe, Bo becomes bo, and the two spellings of Ada both become ada. For text that people enter, casefold is a strong default for case-insensitive comparison. It is designed for Unicode case folding and can be more thorough than lower. Here the simple examples look like lowercase text.

  5. 02:43

    The keys make the intended comparison rule observable, so we can check it before putting it inside sorted. We have not edited any of the original strings. Also notice what the key cannot decide: ada and ADA have the same key. Python's sorting is stable, which means equal-key items keep their previous relative order. That is useful if our input order has meaning. If we need an explicit second rule, such as sorting equal names by an ID, the key can return a tuple of comparison values. We will keep the stable input order in this first solution. Predict whether lowercase ada or uppercase ADA will appear first after sorting: lowercase ada appeared first in the source, so it stays first among equal keys.

  6. 03:30

    First, the names now appear in a human-facing case-insensitive order. The two Ada spellings keep their original relative order because their comparison keys are equal. We kept the original spelling, which was the goal of the opening example. The same key idea now solves the lesson's practical task. Each item is a product dictionary, and the lambda extracts its numeric price for comparison. Pen costs two, Folder five, and Notebook eight, so the printed order matches our price rule. The returned list contains the original records with all their fields. `sorted` gives us a new list; the input order remains Notebook, Pen, Folder.

  7. 04:11

    This matters when another part of a program uses the original order. We also accept a `descending` option and pass it to reverse. The default is False, so callers get ascending price unless they explicitly ask for the opposite. Without a key, dictionaries do not have a natural less-than ordering and a nontrivial `sorted(products)` call raises TypeError. Sorting by each product's name would run but answer a different question. A key is a compact expression of the requirement, not merely a syntactic trick. For a larger record, give the key function a descriptive name and test its returned value.

  8. 04:51

    Check that prices have comparable numeric types before sorting data imported from outside the lesson. The exercise uses valid numeric prices, allowing us to focus on ordering and input mutation. Pen and Pencil have the same price. They keep their input order in both ascending and descending results, because Python's sort is stable for equal keys. Notebook moves to the front only in descending order. The input products list stays unchanged, and an empty input returns an empty list. These checks distinguish the task function from plausible wrong versions: sorting by name, changing the caller's list, or adding an unwanted name tie-breaker.

  9. 05:34

    The last line demonstrates a different API: list.sort changes its list in place and returns None. Assigning its return value is a common error. Choose sorted when you want a new list, and list.sort when you intentionally want to reorder an existing one. Both accept key and reverse. On PythonLessonLab, compare the starter's printed product names with the expected price order, then implement the key and complete the quiz. As an extension, try two products with equal prices and predict which remains first. The useful skill is to separate what you display from what you compare. The key function encodes a real requirement: which property controls order.

Understand the concept

Calling sorted(items) returns a new list in ascending order. Calling items.sort() rearranges that list in place and returns None. Both work naturally for comparable values such as numbers, but Python cannot order whole dictionaries by guessing which field matters. Decide what the ranking means first: product price, a person's name, or a date are different keys.

Pass a function as key so Python evaluates one comparable value for each item. For product dictionaries, key=lambda product: product['price'] selects a numeric price. An ordinary named function works too. Add reverse=True for highest price first. It reverses the order of the chosen keys, not the structure of each record.

Python's sort is stable: records with equal keys keep their relative input order, including when reverse=True is used. This matters when two products share a price. If a second rule is required, choose a tuple key such as (price, name), but do not add a tie-breaker when the original order has meaning. Check the source list after sorting to confirm that sorted() produced a separate list.

The exercise sorts a list of product dictionaries by numeric price. Test ascending and descending output, equal prices, an empty input, and an unchanged source list. Then visit the related problem guide about sorting dictionaries to see why sorted(products) raises TypeError and how choosing a key fixes it.

  • sorted() returns a new list
  • key selects the comparison value
  • reverse=True sorts descending
  • Equal keys retain their relative order

See it step by step

Read the code, predict the output, then compare it with the result.

01. Return a new sorted list

PYTHON
scores = [8, 3, 5]
ordered = sorted(scores)
print(ordered)
print(scores)
EXPECTED OUTPUT
[3, 5, 8]
[8, 3, 5]

sorted() produces an ordered copy. The original scores list still has its initial order.

02. Choose a case-insensitive key

PYTHON
names = ["zoe", "Ada", "bo"]
print(sorted(names, key=str.lower))
print(sorted(names, key=str.lower, reverse=True))
EXPECTED OUTPUT
['Ada', 'bo', 'zoe']
['zoe', 'bo', 'Ada']

str.lower supplies comparison text for each name; reverse changes direction without changing the strings.

03. Keep equal-price products stable

PYTHON
products = [
    {"name": "Pen", "price": 3},
    {"name": "Eraser", "price": 3},
    {"name": "Notebook", "price": 12},
]
ordered = sorted(products, key=lambda item: item["price"])
print([item["name"] for item in ordered])
EXPECTED OUTPUT
['Pen', 'Eraser', 'Notebook']

Pen and Eraser share the key 3, so their input order is preserved.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Choose the value Python should compare

sorted creates a new list and leaves its input alone. Without a key function, strings are compared according to their characters, including case. That can put capitalized names ahead of lowercase names in a way that looks odd in a people-facing report. A key function receives each item and returns the value used for comparison. str.casefold is a useful choice for case-insensitive text sorting because it is designed for caseless matching.

Python calls the key function once for each item, then orders the original items by those computed keys. It does not replace the displayed names with lowercase text. Ada and ada have equal casefolded keys, so their existing relative order is preserved: Python's sorting is stable. Compare the sorted result with the source list below. Then change the first name to uppercase and predict where it will appear.

PYTHON
names = ["zoe", "Ada", "bob", "ada"]
ordered = sorted(names, key=str.casefold)
print(ordered)
print(names)
EXPECTED OUTPUT
['Ada', 'ada', 'bob', 'zoe']
['zoe', 'Ada', 'bob', 'ada']
Follow the reasoning
  1. casefold produces comparison keys without changing the names being returned.
  2. Ada and ada have equal keys, so the stable sort keeps Ada first.
  3. The original list remains in its original order.
02 / 03

Sort records by more than one field

For dictionaries, Python needs a rule for which field to compare. A lambda can pick a field from each record. Returning a tuple gives a sequence of comparison rules: compare the first tuple element and use the second only for ties. Here age is the primary field and name is the tie breaker. This is more reliable than sorting twice unless you carefully account for sort stability and the order in which the sorts happen.

The key function does not need to create a new record. It only describes the value used to order existing records. In the output, Bo comes before Cy because both are 21 and Bo's name sorts first; Ada, age 30, comes last. A missing age key would raise KeyError, so validate or normalize input before using this rule on records from an outside source. Try reversing the two fields in the key tuple and see how the result changes.

PYTHON
people = [
    {"name": "Cy", "age": 21},
    {"name": "Ada", "age": 30},
    {"name": "Bo", "age": 21},
]
ordered = sorted(people, key=lambda person: (person["age"], person["name"]))
print([person["name"] for person in ordered])
print([person["age"] for person in ordered])
EXPECTED OUTPUT
['Bo', 'Cy', 'Ada']
[21, 21, 30]
Follow the reasoning
  1. The first tuple item orders records by age.
  2. The second item resolves the tie between the two 21-year-olds.
  3. The result contains the original dictionary records in their new order.
03 / 03

Use reverse without losing stable ties

reverse=True changes the direction of a sorted result. It does not turn a stable sort into an unstable one: records with equal keys still appear in their original relative order. The practice function sort_products uses each product's price as a key. With descending=True, two equally priced products still retain their input order, which makes a product list predictable.

The built-in sorted returns a fresh list. The list method .sort changes the existing list in place and returns None. Choose sorted when you want to keep a source order for another use, and .sort when changing the source list is intended. A key function should compute a comparison value rather than print, modify records, or depend on changing external state. Add a fourth product priced at 90 and predict its position.

PYTHON
def sort_products(products, descending=False):
    return sorted(products, key=lambda product: product["price"], reverse=descending)

products = [
    {"name": "Ada", "price": 90},
    {"name": "Bo", "price": 70},
    {"name": "Cy", "price": 90},
]
ranked = sort_products(products, descending=True)
print([product["name"] for product in ranked])
print(products[0]["name"])
EXPECTED OUTPUT
['Ada', 'Cy', 'Bo']
Ada
Follow the reasoning
  1. The numeric price is the comparison key.
  2. Descending order puts 90 before 70 but keeps the two 90-priced products in source order.
  3. sorted returns a new list, so the original products list has not changed.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete sort_products(products, descending=False). Return a new list ordered by each product's numeric price. Use descending=True for highest price first. Preserve input order when prices tie, do not mutate the original list, and handle an empty list. The prepared example should print ['Pen', 'Folder', 'Notebook'].
Need a hint?

Return sorted(products, key=lambda product: product['price'], reverse=descending). sorted() preserves equal-key order and leaves products alone.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.What does sorted(products, key=lambda p: p['price']) compare?

    02.How does list.sort() differ from sorted(list)?

    03.What happens to equal-price records under a stable sort?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Trying to compare whole dictionaries

    COMMON MISTAKE
    sorted([{'price': 12}, {'price': 3}])
    FIX
    sorted([{'price': 12}, {'price': 3}], key=lambda item: item['price'])

    What happens: TypeError: '<' is not supported between instances of 'dict' and 'dict'.
    Choose the numeric field that defines the order; dictionaries themselves have no default less-than ordering.

    Assigning the result of list.sort()

    COMMON MISTAKE
    products = [{'price': 12}, {'price': 3}]
    ordered = products.sort(key=lambda item: item['price'])
    print(ordered)
    FIX
    ordered = sorted(products, key=lambda item: item['price'])

    What happens: The call prints None, and products was changed in place.
    Use sorted() when you need a new list and must preserve the input.

    Sorting numeric prices as text

    COMMON MISTAKE
    sorted(['10', '2'])
    FIX
    sorted(['10', '2'], key=int)

    What happens: The text '10' comes before '2' lexicographically.
    Convert numeric text in the key function when numeric order is intended.

    Browse this lesson’s problem fixes

    Sources and further reading