PYTHON / FUNCTIONS
Higher-order functions: map, filter, and sort keys
Pass functions as values to map, filter, sorted, min and max, and control ordering with key functions and tie-breaking tuples.
What you will learn
- Use map/filter for per-element work and remember both return one-shot iterators
- Pass a key function (never its result) to sorted, min, max, and list.sort
- Break ties with tuple keys and negate numbers for descending inside a tuple
- Know that sorted is stable and that key runs exactly once per element
Understanding Higher-order functions: map, filter, and sort keys
A higher-order function is one that takes another function as an argument or returns one. In Python this works because a function object is an ordinary value: `len` without parentheses is the function itself, while `len(x)` is the number it returns. `map(len, words)` therefore hands the function `len` to `map`, which will call it later, once per element, on your behalf.
`map(f, it)` yields `f(x)` for each element and `filter(p, it)` yields only the elements where `p(x)` is truthy. Both are lazy iterators, not lists: nothing is computed until you iterate, and once iterated they are exhausted, so a second pass yields nothing. That laziness is why `print(map(str.upper, words))` shows `<map object at ...>` instead of data, and why you usually wrap them in `list()` or feed them straight into a `for` loop, `sum`, or `join`.
`sorted`, `min`, `max`, and `list.sort` accept a different kind of function argument: `key`, a projection that maps each element to the value you actually want compared. Python calls `key` once per element up front, sorts those computed keys alongside the originals, then returns the originals in the new order, which is why an expensive key costs n calls rather than n log n. Because the sort is stable, elements whose keys compare equal keep their original relative order, and returning a tuple from `key` gives you a primary sort with explicit tie-breakers.
words = ["banana", "Kiwi", "apple", "cherry", "fig"]
print(list(map(len, words)))
print(list(filter(lambda w: len(w) > 3, words)))
print(sorted(words, key=str.lower))
print(sorted(words, key=lambda w: (len(w), w.lower())))
squares = map(lambda n: n * n, [1, 2, 3])
print(list(squares))
print(list(squares))Functions are values you hand to map, filter, and sorted so those tools decide when and how often to call them.
Worked examples
Sorting records and breaking ties
Shows how reverse=True treats equal keys and how a tuple key gives an explicit tie-breaker.
from operator import itemgetter
rows = [
{"name": "zoe", "score": 91},
{"name": "ada", "score": 91},
{"name": "cy", "score": 78},
]
print([r["name"] for r in sorted(rows, key=itemgetter("score"), reverse=True)])
print([r["name"] for r in sorted(rows, key=lambda r: (-r["score"], r["name"]))])
print(max(rows, key=itemgetter("score"))["name"])Example explained
Line 1`itemgetter("score")` builds a function equivalent to `lambda r: r["score"]`, so it is a key function, not a value.
Line 2With `reverse=True` the two 91s stay in input order (zoe before ada) because reversing is applied to the comparison, not to the finished list.
Line 3The tuple key `(-score, name)` sorts scores descending via negation and then names ascending, so ada now comes first.
Line 4`max` uses the same key protocol and returns the first element holding the maximum, which is zoe.
filter(None, ...) and map over two iterables
Demonstrates the truthiness shortcut for filter and what happens when map is given unequal-length inputs.
values = [0, 3, "", "ok", None, 7, []]
print(list(filter(None, values)))
print(list(map(lambda a, b: a * b, [2, 3, 4], [10, 20])))Example explained
Line 1Passing `None` as the predicate means "keep the truthy elements", so `0`, `""`, `None` and `[]` are dropped.
Line 2The kept values are not converted to booleans; the original objects come through unchanged.
Line 3A two-argument lambda needs two iterables, and `map` calls it with one element from each.
Line 4`map` stops at the shortest iterable, so `4` is never paired and the result has only two items.
The key function runs once per element
Counts calls to prove sorted precomputes keys instead of recomputing them during comparisons.
calls = []
def by_length(word):
calls.append(word)
return len(word)
data = ["dd", "a", "ccc", "bb"]
print(sorted(data, key=by_length))
print(len(calls), calls)Example explained
Line 1`key=by_length` passes the function object; adding `()` would call it immediately and fail on the missing argument.
Line 2`calls` shows exactly four invocations, in original list order, before any comparing happens.
Line 3`'dd'` stays ahead of `'bb'` because both have length 2 and the sort is stable.
Line 4This is why a slow key (a database lookup, say) is affordable: it costs n calls, not one per comparison.
Important notes
`sorted` compares the key values with `<`, so a key that returns mixed types (say `int` for some items and `str` for others) raises `TypeError: '<' not supported between instances of 'str' and 'int'`.
`reverse=True` is not the same as `reversed(sorted(...))`: only the former keeps equal-key items in their original relative order.
Common mistakes
Printing `map(...)` or `filter(...)` directly and seeing `<map object at 0x...>`, or iterating the same map twice and getting an empty list the second time, because the iterator is already consumed.
Writing `sorted(words, key=len())` or `key=str.lower(w)`: that calls the function immediately and passes its result, giving a TypeError instead of sorting.
Writing `words = words.sort(key=len)`; `list.sort` mutates in place and returns `None`, so the name now refers to `None` and the data appears to vanish.
Try it yourself
Change, predict, then run
Given `files = ["a.txt", "notes.md", "b.txt", "img.png", "c.md"]`, print only the names ending in `.txt` or `.md` using `filter`, then print all names sorted by extension first and name second using a single tuple key.
Open the Python workspaceCheck your understanding
Two records have the same key value. How does `sorted(rows, key=f, reverse=True)` differ from `list(reversed(sorted(rows, key=f)))`?
- `reverse=True` keeps equal-key records in their original relative order, while reversing the sorted list swaps them
- They always produce identical lists, since both give descending order
- `reverse=True` compares keys backwards and discards records whose keys are equal
- `reversed()` cannot be applied to the list returned by `sorted`
Show answer
Python's sort is stable, and `reverse=True` preserves that stability, so tied records stay in input order; reversing the finished list flips everything including the ties. Option 2 is tempting because both orderings agree whenever all keys are distinct, but that is exactly the case where ties cannot show the difference.