1.2.33 · D5Introduction to Programming (Python)

Question bank — Built-in functions — map, filter, zip, enumerate, sorted, reversed, min, max, sum, any, all

2,102 words10 min readBack to topic

First — the tiny vocabulary every trap uses

The questions below use short variable names on purpose (that is how real Python is written). Here is what each one means before you meet it in a trap — no prior Python idiom assumed:

Three behaviour families — the visual taxonomy

Every one of the 11 built-ins falls into exactly one of three buckets. The figure below is the whole page in one picture — glance at it, then the traps will feel like variations on these three shapes.

Figure — Built-in functions — map, filter, zip, enumerate, sorted, reversed, min, max, sum, any, all
Figure — Built-in functions — map, filter, zip, enumerate, sorted, reversed, min, max, sum, any, all

True or false — justify

map(f, xs) returns a list.
False — it returns a lazy iterator (the conveyor belt); you only get a list if you wrap it in list(...). Printing it raw shows <map object ...>.
sorted(xs) changes the original list xs.
False — sorted is the photocopier: it builds and returns a new list; the original is untouched. It is xs.sort() that scribbles on the original in place.
all([]) is True.
True — all is AND over the items, and AND's identity element is True, so "every one of zero items is truthy" is vacuously satisfied.
any([]) is True.
False — it is False; any is OR over the items, and OR's identity element is False, so with nothing to make it true it stays false.
filter(None, xs) removes the None values from xs.
False (misleading name) — passing None as the predicate drops every falsy item (0, '', [], None, False), not only None.
zip(a, b) raises an error when a and b have different lengths.
False — internally zip pulls ONE item from a, then ONE from b, in a loop; the moment either runs dry it stops the whole loop, so the shorter one dictates the length and the leftover items of the longer are never pulled.
enumerate(xs) always starts counting at 1.
False — it starts at 0 by default; you pass start=1 explicitly if you want 1-based indexing.
reversed(xs) works on any iterable.
False — it needs a sequence with a known length (list, tuple, str, range) so it can walk from the last index backwards. A plain generator has no length or end, so reversed on it raises TypeError.
sum(['a', 'b']) gives 'ab'.
False — sum starts its accumulator at the number 0, so the very first step 0 + 'a' raises TypeError. Use ''.join(...) for strings.
You can loop over the same map object twice and get the same result both times.
False — a map object is a single-use conveyor belt; the first full pass exhausts it, and the second pass yields nothing. (See the lifecycle figure below.)
max and min on an empty list return 0.
False — they raise ValueError on an empty iterable (there is no element to return). Supply a default= argument to avoid the error.
sorted is guaranteed to keep equal-key items in their original relative order.
True — Python's sort is stable, so items comparing equal under the key stay in their input order.

Spot the error

Each item below shows the buggy line, then a mini step-by-step trace of how it goes wrong, then the fix.

xs = xs.sort() — what breaks?
Trace: xs.sort() sorts xs in place and returns NoneNone is assigned back to xsxs is now None, not a list. Fix: xs = sorted(xs), or call xs.sort() on its own line and don't assign it.
print(map(str, [1,2,3])) — why not the numbers?
Trace: map(...) builds the conveyor belt object but pulls nothing off it → print shows the object itself → output <map object at 0x...>. Fix: print(list(map(str, [1,2,3]))) forces the belt to run and collects ['1','2','3'].
sorted(xs, key=lambda x: x > 5) to keep only items above 5 — what's wrong?
Trace: key maps each x to True/False (its comparison rank), it never drops anything → items with key False sort before key True, so nothing is removed. Fix: use filter(lambda x: x > 5, xs) (or a comprehension) to actually remove items.
m = map(int, data); list(m); list(m) gives [] the second time — why?
Trace: line 2 list(m) walks the belt end-to-end → belt now empty → line 3 list(m) finds nothing left → []. Fix: materialize once — m = list(map(int, data)) — then reuse the list as many times as you like.
max(people, key=len) where people = [('Ann',30),('Bo',25)] to find the oldest — bug?
Trace: len(('Ann',30)) is 2 and len(('Bo',25)) is also 2 → every item ties → max returns the first tied item, ('Ann',30), not the oldest. Fix: key=lambda p: p[1] to compare by the age field.
total = sum(map(square, nums)); avg = total / len(map(square, nums)) — why does len fail?
Trace: len needs to know a count up front, but a map object never computes its length → Python raises TypeError: object of type 'map' has no len(). Fix: avg = total / len(nums) (the input list does have a length), or build a list first.
for i in range(len(xs)): print(i, xs[i]) — what's the cleaner, safer form?
for i, v in enumerate(xs): print(i, v) — no index math, no chance of an off-by-one or IndexError, and it works on any iterable without needing [i].
nums, letters = zip(pairs) to unzip [(1,'a'),(2,'b')] — why one column only?
Trace: without * you passed ONE argument, so zip iterates pairs itself and tries to unpack its two tuples into nums, letters → not the transpose you wanted. Fix: zip(*pairs) spreads the pairs into separate arguments so it pairs column-wise → (1,2) and ('a','b').

Why questions

Why is map lazy instead of eagerly building a list?
To save memory and time — it produces each result only when asked, so you can chain it or feed it into sum/any without ever materializing the whole sequence.
Why does any stop as soon as it hits a truthy item?
Because OR is already decided once one operand is true — no later item can change the answer, so short-circuiting saves work. Likewise all stops at the first falsy item.
Why prefer sorted(xs) over xs.sort() sometimes?
sorted returns a value while leaving the original intact, so you can keep the unsorted data and the sorted view. .sort() destroys the original ordering.
Why does filter(None, xs) exist as a shortcut?
It's the common "drop the empty/zero/None junk" operation; passing None says "use each item's own truthiness as the predicate" — using exactly the falsy list from the truthiness figure above.
Why does sum take a start argument?
start is the initial accumulator value, letting you offset the total (sum(xs, 100)) or set a different starting point when accumulating — though for strings you should still use join (starting at 0 clashes with text).
Why does enumerate beat a separate counter variable you increment yourself?
It couples the index to the loop so you can't forget to increment or accidentally increment twice; it reads as what you mean and is a one-liner.
Why does key receive one item at a time rather than the whole list?
Because sorting/min/max compare items pairwise, so each item must be independently mapped to its comparison value; key(item) is that per-item projection.

Edge cases

What does list(zip()) (no arguments) give?
An empty list [] — with nothing to pair, there are zero tuples to produce.
What does all([0]) return, and why not all counting length?
Falseall tests truthiness, and 0 is falsy; the list is non-empty but its single item fails the test.
What does any([[], '', 0]) return?
False — every item is falsy (empty list, empty string, zero), so no item makes the OR true.
What does sum([]) return?
0 — the default start accumulator, since adding nothing to the initial 0 leaves 0.
What does max([], default=-1) return?
-1 — with default supplied, an empty iterable yields that fallback instead of raising ValueError.
What does min([], default=99) return, and how does it differ from min([])?
99min accepts the same default= escape hatch as max; without it, min([]) raises ValueError for the identical "no element to return" reason.
What happens with min([3, 3, 3], key=abs) when all keys tie?
It returns the first element that achieves the minimum key, so ties are broken by original position (same stable-first rule as sorted).
What does list(map(lambda a, b: a+b, [1,2,3], [10,20])) give?
[11, 22] — with multiple iterables map stops at the shortest, exactly like zip, so the trailing 3 is dropped.
What does list(reversed(range(3))) give, and why is range allowed?
[2, 1, 0]range is a proper sequence with a known length and indexing, so reversed can walk it back-to-front without materializing it first.

The exhaustion lifecycle — see it once, never forget it

The single most common trap is reusing a lazy iterator. The figure traces a map object across two list(...) calls so you can see the belt empty out.

Figure — Built-in functions — map, filter, zip, enumerate, sorted, reversed, min, max, sum, any, all

Recall One-line rule to survive every trap

Ask three questions of any built-in call: Is it lazy? (conveyor belt — wrap in list, use once), Does it mutate or return? (sorted returns a copy, .sort() scribbles on the original), Does it test truthiness? (filter/any/all follow the falsy list: 0 '' [] {} () None False).