Worked examples — Built-in functions — map, filter, zip, enumerate, sorted, reversed, min, max, sum, any, all
This page drills the parent topic until no case can surprise you. We first lay out a scenario matrix — every kind of situation these 11 functions can be thrown into — then work examples that hit every cell.
Before we start, one word we will lean on: an iterable is anything you can walk item-by-item (a list, a string, a range). An iterator is a one-shot walker over an iterable — it hands you items one at a time and, once emptied, stays empty (see Iterators and generators). Whenever a function below is called "lazy", it hands back an iterator, so we wrap it in list(...) to force the items into view.
Two more small tools we will meet along the way (both explained in full when they first appear):
- The ==star
*== in front of a list "spreads" it into separate arguments. Think of*[1,2,3]as pouring the box out so a function receives1, 2, 3as three separate arguments rather than one list. (Example 8 builds this visually before using it.) - A generator expression looks like a list comprehension but with round brackets:
(x for x in xs if cond). It is lazy — it produces items one at a time on demand instead of building a whole list first (contrast with List comprehensions, which build the full list eagerly). (Example 6 explains this before using it.)
The scenario matrix
Every row is a case class — a distinct situation that behaves differently. The map below is our checklist: each leaf is one cell, and the example that fills it is named on the leaf. When every leaf is ticked, no scenario is left uncovered.
| Cell | Case class | What makes it tricky | Covered by |
|---|---|---|---|
| A | Empty input | Crash, or sensible default? | Ex 1 |
| B | Single element | Reducers with one item | Ex 1 |
| C | Unequal-length iterables (zip/map) |
Which one decides when to stop? | Ex 2 |
| D | Truthiness edge cases (filter(None,…)) |
0, '', [], None are all falsy |
Ex 3 |
| E | Vacuous truth (all([]), any([])) |
Empty logic defaults | Ex 3 |
| F | key= for order vs. filter for removal |
The classic confusion | Ex 4 |
| G | Stability + ties in sorted |
Equal keys keep original order | Ex 4 |
| H | Consuming a lazy iterator twice | Second pass sees nothing | Ex 5 |
| I | Negative numbers / signs in a reduce | min/max/sum across mixed signs |
Ex 6 |
| J | Wrong start / type mismatch (sum of strings) |
Silent-looking bug that raises | Ex 6 |
| K | Real-world word problem (chaining) | Combining tools into a pipeline | Ex 7 |
| L | Exam-style twist (zip(*…) unzip, short-circuit) |
Non-obvious mechanics | Ex 8 |
| M | reversed vs sorted(…, reverse=True) |
Back-to-front walk vs. re-ordering | Ex 9 |
Example 1 — Empty & single-element inputs (cells A, B)
Steps.
-
sum([])→0. Why this step?sumstarts from an accumulator calledstart, and its default is0. Adding nothing to0leaves0. Nothing to add ⇒ you get the identity of+. -
any([])→False. Why this step?anyis a chain of ORs. With no terms, the answer is the identity of OR, which isFalse(see Truthiness in Python). -
all([])→True. Why this step?allis a chain of ANDs. No terms ⇒ identity of AND ⇒True. This is vacuous truth — "every item is truthy" is trivially satisfied when there are no items to violate it. -
max([7])→7. Why this step? One element is trivially the largest of one element. This is the single-element case:minandmaxcollapse a one-item list to that item. -
max([])raisesValueError— there is no "largest of nothing". To ask safely, passdefault:max([], default=0)→0. Why this step? Unlikesum,max/minhave no natural identity element (what's the "largest possible number"?), so Python forces you to name a fallback.
Example 2 — Unequal-length iterables (cell C)
Steps.
-
zippairs by position:(1,10), (2,20), (3,30). Why this step?zipwalks all inputs in lockstep and stops the instant the shortest runs dry. Oncebis exhausted after 3 items, there is no partner fora's4and5, so they are silently dropped. -
mapwith two iterables behaves the same way:1+10, 2+20, 3+30→[11, 22, 33]. Why this step? Multi-iterablemapfeeds one item from each input intof; likezip, it halts at the shortest. So4and5never reach the lambda.
The figure below makes the truncation visible: the top row is a, the bottom row is b, the yellow arrows show which positions get paired, and the pink 4 and 5 at the far right have no partner below them, so they fall out entirely.

Notice in the figure that only three yellow arrows are drawn — one per surviving pair — confirming zip produced a length-3 result, not length-5.
Example 3 — Truthiness & vacuous logic (cells D, E)
Steps.
-
filter(None, data)keeps each item that is truthy. The falsy values in Python are0,'',[],None(andFalse,0.0). So they get dropped, leaving['hi', [1], 3]. Why this step? PassingNoneas the predicate means "use the item's own truthiness as the test" — a shorthand forfilter(lambda x: x, data). -
any(data)→True. Why this step?anyreturnsTrueas soon as it meets one truthy item.'hi'(index 2) is truthy, so it short-circuits there and returnsTrue. -
all(data)→False. Why this step?allreturnsFalseat the first falsy item. The very first item0is falsy, soallstops immediately and returnsFalse.
In the figure below, each item sits in a box coloured pink for falsy and blue for truthy. The blue boxes ('hi', [1], 3) are exactly what filter(None,…) keeps. The pointer from any lands on the first blue box (index 2 — where it stops and returns True); the pointer from all lands on the first pink box (index 0 — where it stops and returns False).

Recall Why the empty defaults are consistent
On an empty list, any never finds a truthy item ⇒ False (cell E); all never finds a falsy item ⇒ True. Same rules, no items to trip them.
Example 4 — key orders, filter removes; and ties (cells F, G)
Steps.
-
key=lambda x: x % 2 == 0maps each item toTrue/False(1/0).sortedorders by that key: allFalse(odd) items first, then allTrue(even) items. Why this step?keynever removes anything — it only computes a sort value. Sowrongstill contains all six numbers, just reordered into odds-then-evens. -
Within each key-group, stability preserves the original relative order. Odds appear as they did (
5, 1, 7), then evens (2, 8, 4). Sowrong == [5, 1, 7, 2, 8, 4]. Why this step? Python's sort is stable: items with equal keys keep their input order. Every odd shares keyFalse, every even sharesTrue, so ties = original order. -
To actually drop the odds, use
filter:right == [2, 8, 4]. Why this step?filterkeeps items where the predicate is truthy and discards the rest — that is the removal tool, notkey.
Example 5 — A lazy iterator is single-use (cell H)
Steps.
-
map(...)builds a lazy iterator — it computes nothing yet. Why this step? Laziness means work happens only when items are pulled. Right nowsquaresis a promise, not a list. -
list(squares)pulls every item:first == [1, 4, 9]. This drains the iterator to empty. Why this step? An iterator has an internal position; forcing it to the end leaves the position past the last item, with nothing behind it. -
list(squares)again finds nothing left:second == []. Why this step? Iterators do not rewind. The secondlist(...)walks an already-exhausted stream, so it collects an empty list.
Example 6 — Signs in reducers, and sum type mismatch (cells I, J)
Before this example uses a generator expression, let's earn the notation. A generator expression is written (x for x in xs if cond) — round brackets, one item produced on demand. It is the lazy cousin of a list comprehension [x for x in xs if cond] (square brackets, whole list built at once). We feed a generator expression to max here so no throwaway list is built — max simply pulls items one at a time and remembers the largest. See Iterators and generators for the lazy machinery.
Steps.
-
min(temps)scans for the most-negative value:-9. Why this step?mincompares numerically; more negative = smaller. Signs are handled by ordinary<, no special case needed. -
max(temps)→6;sum(temps)→-4 + -9 + 3 + 0 + -1 + 6 = -5. Why this step?sumfolds+across the list starting from0; negatives just subtract. This is the mixed-sign case working out to a negative total. -
max(t for t in temps if t < 0)→-1. Why this step? The generator expression(t for t in temps if t < 0)lazily yields only the negatives (-4, -9, -1);maxpulls them one by one and keeps the largest, which among negatives is the one closest to zero. -
sum(['a','b','c'])raisesTypeError. Why this step?sumbegins atstart=0(anint), then computes0 + 'a'— you can't add a number to a string. For strings use''.join([...]), which gives'abc'.
Example 7 — Real-world pipeline (cell K)
Steps.
-
Pair names with marks:
zip(names, marks)→('Ana',72), ('Ben',35), …. Why this step? The two lists carry one student's data across two places;zipstitches them into single records so we can sort them as units. -
Keep passers:
filter(lambda p: p[1] >= 40, pairs)keepsAna(72), Cy(58), Deb(40). Why this step? Removal isfilter's job (neverkey).Ben(35)andEli(39)fall below 40 and are dropped. -
Rank highest-first:
sorted(passers, key=lambda p: p[1], reverse=True)→Ana(72), Cy(58), Deb(40). Why this step?keypicks the sort field (the mark, index 1);reverse=Trueflips to descending so the top scorer leads. -
Attach rank:
enumerate(ranked, start=1)→(1,('Ana',72)), (2,('Cy',58)), (3,('Deb',40)). Why this step?enumeratesupplies a 1-based counter alongside each record — cleaner thanrange(len(...)), which is error-prone. See how this chains into List comprehensions and Lambda functions.
board = list(enumerate(
sorted(filter(lambda p: p[1] >= 40, zip(names, marks)),
key=lambda p: p[1], reverse=True),
start=1))
# [(1,('Ana',72)), (2,('Cy',58)), (3,('Deb',40))]Example 8 — Exam twist: unzip with *, and true short-circuit (cell L)
First we earn the ==star *==. When you put * in front of a list inside a function call, Python "spreads" the list into separate positional arguments. So zip(*[(1,'a'),(2,'b'),(3,'c')]) is exactly zip((1,'a'), (2,'b'), (3,'c')) — three separate arguments, not one list argument. The figure shows this pouring-out: the boxed list on the left is emptied into three loose tuples on the right, which then feed zip.

Steps.
-
*pairsspreads the list into separate arguments, sozip(*pairs)iszip((1,'a'), (2,'b'), (3,'c'))(as the figure showed). Why this step?zipnow treats each tuple as an input and pairs them column-wise: first elements together, second elements together. This inverts the original zipping. -
Result:
nums == (1, 2, 3),letters == ('a', 'b', 'c'). Why this step? Column 0 is all the numbers, column 1 is all the letters — the two columns are exactly the originals we zipped from. -
True short-circuit.
all(positive(x) for x in [4, 5, -1])receives a generator expression (lazy), soallpulls values one at a time.positive(4)→True,positive(5)→True,positive(-1)→False; at that firstFalse,allstops and returnsFalse. Crucially, all three were truthy-tested here, but the short-circuit meansallnever pulls a fourth item even if the generator could produce more. Why this step? Because the generator is lazy,positiveruns only whenallasks. Compare this with the broken attempt below, where an eager list defeats short-circuiting entirely.
Example 9 — reversed vs sorted(…, reverse=True) (cell M)
Steps.
-
reversed(xs)walks the existing order from the last item to the first:xsis[3, 1, 2], so back-to-front is[2, 1, 3]. Why this step?reverseddoes no ordering — it only flips the direction of travel. It needs a sequence with a known length (list, tuple,range,str) so it can start from the end and step backwards. -
sorted(xs, reverse=True)first sorts ascending (1, 2, 3) then flips to descending:[3, 2, 1]. Why this step?reverse=Truereverses the sorted result, not the original. This is whywalk_back([2,1,3]) anddescending([3,2,1]) differ — one reflects input order, the other reflects magnitude. -
''.join(reversed('chalk'))walks the characters back-to-front (k, l, a, h, c) and joins them:'klahc'. Why this step? A string is a sequence, soreversediterates its characters end-to-start;''.join(...)glues that iterator back into a string. -
reversed(xs)returns a lazy, single-use iterator — just likemap. Consume it once (list(...)) or it empties out, exactly the trap from Example 5. Why this step?reversedshares the one-shot nature of the other lazy tools, so the same "materialise once" advice applies.
The figure contrasts the two operations on [3, 1, 2]: the top track shows reversed simply reading the same boxes right-to-left (2, 1, 3); the bottom track first sorts into 1, 2, 3 and then flips to 3, 2, 1. Different results from different intents.

Recall One-line summary of the matrix
Empty ⇒ know each default (sum→0, any→False, all→True, min/max crash unless you pass default=). zip/map stop at the shortest input. filter(None,…) drops falsy values (0, '', [], None). key only orders, filter removes; sort is stable so ties keep input order. Lazy iterators (map, filter, zip, enumerate, reversed) run once — materialise with list(...) to reuse. sum of strings is a TypeError (use ''.join). *pairs spreads a list into separate arguments, so zip(*pairs) unzips column-wise. Generator expressions (x for x in xs) are lazy, which is what lets any/all short-circuit. reversed mirrors input order (back-to-front), while sorted(..., reverse=True) mirrors value order (descending) — they agree only on already-sorted input.