What does the key= argument of Python's sorted() do?
answer
- Order by something other than the element
- A callable taking one argument
- Applied to every element before comparing
- Originals returned, not the computed values
- str.lower gives case-insensitive order
basics
~10 skey= 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 lineswords = ["banana", "apple", "Cherry"]
print(sorted(words))
print(sorted(words, key=str.lower))
print(max(words, key=len))go deeper
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.
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.
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.
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