skip to content

Key Functions for sorted, min, and max

sorted, min and max take a key callable that maps each element to the value actually compared, evaluated once per element. Interviewers use it to test idiom and complexity awareness at the same time.

part ofPythonoverview, primer and where to startread it →
on this pageshow

questions

3

What does the key= argument of Python's sorted() do?

level: juniorimportance: must knowfreq 72%

answer

  1. Order by something other than the element
  2. A callable taking one argument
  3. Applied to every element before comparing
  4. Originals returned, not the computed values
  5. str.lower gives case-insensitive order

basics

~10 s

key= takes a one-argument callable applied to every element; sorted, min and max then compare the values it returns instead of the elements themselves. The elements come back unchanged, only reordered.

solid answer

~40 s

`key` is a keyword-only argument on `sorted()`, `min()` and `max()` that takes a callable of **exactly one argument**. Each element is passed through it, and the returned sort key is what Python compares — but the call still yields the original objects, so `sorted()` returns a new `list` of the same elements, reordered, and `min()`/`max()` return an element, not a key. `sorted(words, key=str.lower)` gives case-insensitive order; `max(records, key=lambda r: r['score'])` gives the record with the highest score. Returning a `tuple` orders by several fields at once, because tuples compare component by component. It is not a two-argument comparator: Python 3.0 removed that parameter, and a one-argument key is what replaced it.

code

python · 5 lines
python
words = ["banana", "apple", "Cherry"]

print(sorted(words))
print(sorted(words, key=str.lower))
print(max(words, key=len))

go deeper

for a junior

Be ready to define key= in one sentence and write sorted(words, key=str.lower) unaided. Remember that sorted() hands back a new list of the original elements, never the computed key values.

for a middle

Explain the callable contract: exactly one argument, invoked for every element, and its return value is what gets compared. Show that returning a tuple gives multi-field ordering, and know that min()/max() accept the same argument.

for a senior

Show judgement about what belongs in a key: pure, deterministic, cheap to compute and cheap to compare. Be able to say why Python 3 kept a one-argument key rather than a comparator, and how a key normalizes otherwise-unorderable data.

for a principal

Treat sort keys as an ordering policy the codebase owns. Ad-hoc lambdas at every call site drift apart, so decide where a shared, tested key function lives when the same ordering appears in exports, APIs and UI listings.

### What `key=` actually is `sorted()`, `min()` and `max()` all accept a keyword-only argument named `key`. It takes a **callable of exactly one argument**. Before any ordering happens, that callable is applied to each element of the iterable, and the value it returns — the *sort key* — is what Python actually compares. The elements themselves are never modified and never replaced: `sorted()` returns a new `list` containing the **original** objects, just rearranged, and `min()`/`max()` return the original element whose key was smallest or largest. The default is no key at all, in which case the elements are compared directly using their own ordering protocol (`__lt__`). Supplying `key` is how you say "order these by something other than themselves". ```python words = ["banana", "apple", "Cherry"] sorted(words) # ['Cherry', 'apple', 'banana'] - uppercase sorts first sorted(words, key=str.lower) # ['apple', 'banana', 'Cherry'] max(words, key=len) # 'banana' ``` ### The callable contract Four properties are worth stating explicitly in an interview: 1. **One argument.** The key callable receives a single element. It is *not* a two-argument comparator; passing a function that expects two arguments raises `TypeError` at the first call. Python 3.0 removed the old two-argument comparison parameter precisely because a one-argument key is cheaper and easier to reason about. 2. **Any object may be returned**, as long as the returned objects are mutually comparable with `<`. Returning a `tuple` is the idiomatic way to order by several fields at once: tuples compare element by element, so `key=lambda r: (r[1], r[0])` orders by the second field and breaks ties with the first. 3. **The result is discarded.** Keys exist only for the duration of the call. If you need the key values afterwards, compute them yourself into a list of pairs. 4. **It should be pure and deterministic.** A key that mutates its argument, or that returns a different value on a second call, produces an ordering nobody can reproduce. ### Where it shows up The same parameter appears across the standard library's ordering surface: `sorted()`, `min()`, `max()`, `heapq.nsmallest()` and `heapq.nlargest()` all take it, with the same one-argument contract. Learning it once buys the whole family. `min()` and `max()` additionally accept a keyword-only `default` value, used when the iterable turns out to be empty — a common pairing when you are taking the best element of a filtered batch that may filter down to nothing. ### The idioms an interviewer wants to hear * **Case-insensitive ordering:** `key=str.lower` (or `str.casefold` for non-English text). * **Order by length:** `key=len`. * **Order by distance from zero:** `key=abs`. * **Order a dictionary by its values:** `sorted(scores.items(), key=lambda kv: kv[1])` — iterating a dict yields only its keys, so the classic "sort a dict by value" answer is to sort `dict.items()` with a key that selects the second half of each pair. * **Multi-field ordering:** return a tuple. * **Normalizing mixed data:** a list holding both `int` and `str` raises `TypeError` when sorted directly, because the two types have no defined ordering between them. A key that maps every element into one comparable space — `key=str`, say — makes the sort well-defined again, though you should ask whether the mixed list is itself the bug. ### The mistakes The two misconceptions worth naming out loud are (a) believing `sorted()` returns the *key* values rather than the elements — it does not, which is exactly why the pattern is useful — and (b) believing `key` is a comparison function. A candidate who says "key takes the two items being compared and returns -1, 0 or 1" is describing a different language's API, and it signals they have never actually written the call. A third, subtler one: `key` does not filter. Returning `None` from the key for elements you want to skip does not drop them; it puts them all in one group and then usually raises `TypeError` when `None` is compared with anything else. Filter first, then sort.

  • If key= changes what is compared, does sorted() give you back the transformed values?
    No. The keys exist only inside the call: they are computed, compared, then discarded, and `sorted()` returns a new list of the original elements in their new order. `min()` and `max()` likewise return an element, not its key. If you need the keys afterwards, build a list of `(key, item)` pairs yourself.
  • What happens when you call sorted() on a list mixing str and int, and how does key= help?
    It raises `TypeError`, because `str` and `int` define no ordering between them. A key that maps everything into one comparable space — `key=str`, or a function returning a `(type_rank, value)` tuple — makes the ordering well defined. Usually the better question is why one list holds both types.
  • How do you order by two fields at once with a single key callable?
    Return a `tuple`. Tuples compare component by component, so `key=lambda r: (r['city'], r['name'])` orders by city and breaks ties by name. Every component must be comparable with its counterpart in the other tuples, or the comparison of a tie falls through and raises `TypeError`.

It is like telling a librarian to shelve books by the author's surname rather than by the title on the spine: the books themselves are untouched, only the label used to decide their order changes.

saying these in an interview costs you the question

  • Describes key= as a two-argument comparison function
  • Thinks sorted() returns the key values instead of the elements
  • Believes key= mutates or replaces the original elements
  • Says min() and max() have no key= parameter
  • Uses key= to filter elements out of the result

context

open as a page

How many times does sorted() call the key= function, and why does it matter?

level: middleimportance: should knowfreq 48%

basics

~20 s

Exactly once per element: n calls for n elements. CPython computes every key up front into an internal array and then compares only those key values, so an expensive key costs O(n), not O(n log n).

open as a page

How do you make sorted() order one field descending and another ascending?

level: seniorimportance: should knowfreq 45%

basics

~10 s

reverse= flips the whole ordering, never one field. Either negate the numeric field inside a tuple key, as in key=lambda r: (-r.score, r.name), or run two stable sorts, least significant field first.

open as a page