skip to content

Iteration Builtins

The built-in callables that drive an iterator on your behalf: next with a fallback value, any and all stopping early, and the two-argument iter that reads until a sentinel appears.

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

questions

11

What do Python's any() and all() return for an empty iterable?

level: juniorimportance: must knowfreq 62%

answer

  1. Write out the loop each one is
  2. Which one needs to find a counterexample?
  3. Nothing failed, so the claim holds
  4. Vacuous truth over zero elements
  5. all([]) and any([]) disagree

basics

~10 s

any() returns False and all() returns True on an empty iterable. all() is vacuously true because no element failed the test; any() found no truthy element, so there is nothing to report as True.

solid answer

~50 s

Both builtins reduce an iterable to a single `bool`, and their empty-input answers fall straight out of their definitions. `any()` scans for the first truthy element and returns `True` there, otherwise falls through to `False` — an empty iterable never enters the loop, so `any([])` is `False`. `all()` scans for the first falsy element and returns `False` there, otherwise falls through to `True`, so `all([])` is `True`. That is vacuous truth: over zero elements, nothing fails. Both also short-circuit, stopping at the first decisive element rather than reading the whole input, and both return a real `bool`, never the element that decided it. The practical trap is validation code shaped like `if all(check(x) for x in batch)`, which silently approves an empty batch — guard the emptiness separately when "at least one" is part of the requirement.

code

pycon · 8 lines
pycon
>>> any([])
False
>>> all([])
True
>>> all(x > 0 for x in [])
True
>>> any(x > 0 for x in [])
False

go deeper

for a junior

Recall the two results cold: any([]) is False, all([]) is True. Be ready to justify the second one in a sentence — no element failed, so the claim holds.

for a middle

Explain the mechanics behind the results by writing out the equivalent loop, and mention short-circuiting: both stop at the first decisive element and both return a bool rather than the element itself.

for a senior

Show where the empty case causes real damage — a permission or validation gate built on all() that silently approves an empty requirement list — and how you would make the non-empty requirement explicit in review.

for a principal

Own the convention argument: vacuous truth is what preserves all(xs + ys) == all(xs) and all(ys), so codebases should encode "at least one" as its own check rather than redefining the reduction with a helper everyone must remember.

`any(iterable)` and `all(iterable)` are builtins that collapse an iterable of arbitrary values into a single `bool`. Almost every question about them is answered by writing out the two loops they are equivalent to: ```python def any_(iterable): for element in iterable: if element: return True return False def all_(iterable): for element in iterable: if not element: return False return True ``` ### Why the empty answers are what they are If the iterable yields nothing, neither loop body runs even once, so control falls through to the final `return`. `any()` falls through to `False`; `all()` falls through to `True`. There is no special case in the C implementation for empty input — the answers are the natural identity element of each reduction, exactly as `sum([])` is `0` and `math.prod([])` is `1`. The `all()` case is the one candidates get wrong, and it has a name: vacuous truth. `all()` asserts "no element fails", and over zero elements nothing fails, so the assertion holds. The convention is not arbitrary; it is what keeps the useful algebraic property that splitting an input in two never changes the answer. `all(xs + ys)` equals `all(xs) and all(ys)` for every split, including the split where one half is empty — which only works if the empty half evaluates to `True`. The mirrored property holds for `any()` with `or` and `False`. The two are duals, related by De Morgan's laws: `all(xs)` is the same as `not any(not x for x in xs)`, and `any(xs)` is the same as `not all(not x for x in xs)`. Recognising that pairing is often enough to reconstruct any of the four empty-input answers under interview pressure. ### Short-circuiting Neither builtin promises to read the whole input. `any()` returns the moment it sees a truthy element; `all()` returns the moment it sees a falsy one. The number of elements actually examined is therefore data-dependent — anywhere from one to all of them. This is why the argument you hand them matters so much: a lazy source lets the short-circuit skip work, while an eagerly built container has already paid for every element before the call begins. Because they consume the iterable through the normal iteration protocol, they work with any iterable — a `list`, a `set`, a file object, a generator. When the argument is a one-shot iterator, short-circuiting leaves it partially consumed rather than exhausted, which matters if anything downstream iterates it again. ### The return value is a bool, not an element A frequent misconception is that `any()` behaves like a chain of `or` operators. It does not. The `or` and `and` operators return one of their *operands* (`0 or 'x'` is `'x'`), whereas `any()` and `all()` always return the canonical `True` or `False`. If you need the element that decided the outcome rather than the verdict, `any()` is the wrong tool. What counts as truthy is decided by the standard truth test applied to each element — empty containers, zero and `None` are falsy, most other objects are truthy — so `any(['', 0, 'x'])` is `True` because the last element is a non-empty string. ### Where the empty case actually bites The realistic bug is a validation or gating check written as: ```python if all(passes(item) for item in batch): approve(batch) ``` An empty `batch` approves. The same shape appears in permission checks (`all(user.has(p) for p in required)` grants everything when `required` is empty) and in health checks (`all(probe.ok for probe in probes)` reports healthy when no probes were registered). The fix is to make the emptiness requirement explicit rather than hoping the reduction covers it: test the container first with `if batch and all(...)`, or count the items in the same pass when the source is a one-shot iterator you cannot cheaply re-test. Symmetrically, `any()` on empty input returning `False` is usually the harmless direction — it fails closed — which is part of why `all()` is where the surprise lives. This behaviour has been stable for the entire Python 3 line and is unchanged on 3.14; it is one of the few corners of the language with no version story at all. ### Reading them in real code Because both take an iterable rather than a variadic argument list, `any(a, b)` is a `TypeError`, not a two-way or; the arguments must be wrapped in a container or produced by an expression. And because they consume the iterable through ordinary iteration, they work on a `dict` (iterating its keys), a `set`, a file object (its lines) and any user-defined object implementing `__iter__` — the truth test is applied to whatever those yield, which is why `any(some_dict)` answers "is any key truthy" and not "is the dict non-empty". When emptiness is the actual question, `if container:` says it directly.

  • Does any() evaluate every element before it returns?
    No. It returns `True` the moment it sees a truthy element and never looks at the rest, so the number of elements examined depends on the data. `all()` mirrors this and stops at the first falsy element. Only when the answer is `False` for `any()` (or `True` for `all()`) has the whole iterable been consumed.
  • How would you require that a batch is non-empty and every item passes?
    Make the emptiness requirement explicit rather than leaning on the reduction: `if batch and all(check(x) for x in batch)`. If the source is a one-shot iterator you cannot re-test, count while you check — for example total the passes and the items in one loop, then require the count to be positive and equal.
  • How are any() and all() related to each other?
    They are De Morgan duals: `all(xs)` equals `not any(not x for x in xs)`, and `any(xs)` equals `not all(not x for x in xs)`. That relationship also explains the empty-input answers — negating `False` for empty `any()` gives `True` for empty `all()`.

"Every parcel in this van is damaged" is true of an empty van — you cannot point at a parcel that isn't. "Some parcel in this van is damaged" is false for the same reason.

saying these in an interview costs you the question

  • Says all() on an empty list returns False
  • Says any() or all() raises on empty input
  • Claims both always scan the entire iterable
  • Thinks any() returns the truthy element, not a bool
  • Treats any(x) as a non-empty check on the container
  • Confuses them with the or/and operators returning operands

context

open as a page

What does enumerate(items, start=1) give you that range(len(items)) does not?

level: juniorimportance: must knowfreq 75%

basics

~20 s

enumerate yields (index, item) pairs straight from any iterable, so the loop body never indexes back into the sequence and the object needs no len(). The start argument only shifts the counter; it never skips items.

open as a page

What does next(it, default) return when the iterator is already exhausted?

level: juniorimportance: must knowfreq 45%

basics

~10 s

It returns the second argument. With only one argument, next raises StopIteration on an exhausted iterator; supplying a fallback turns exhaustion into an ordinary return value. Any other exception the iterator raises still propagates.

open as a page

Why does any() lose its short-circuit benefit when passed a list comprehension?

level: middleimportance: must knowfreq 55%

basics

~20 s

Arguments are evaluated before the call. A list comprehension runs the predicate on every element and allocates the whole list first, so any() short-circuits over a result that has already cost full time and memory.

open as a page

Why can zip() silently drop data, and how does zip(strict=True) prevent it?

level: middleimportance: must knowfreq 60%

basics

~20 s

zip stops the moment its shortest input is exhausted, so trailing items of longer inputs are dropped with no warning. Since Python 3.10, passing strict=True makes zip raise ValueError when the inputs turn out to be unequal in length.

open as a page

Why does sum() over a generator of booleans work as a match counter in Python?

level: middleimportance: should knowfreq 38%

basics

~20 s

bool is a subclass of int, with True equal to 1 and False to 0, so adding booleans totals the matches. sum(pred(x) for x in items) counts without building a list, and returns an int.

open as a page

Why does reversed() raise TypeError on a generator object but work on a list?

level: middleimportance: should knowfreq 42%

basics

~20 s

reversed() must walk an object backwards, so it needs either a reversed method or the sequence protocol: len plus integer getitem. A generator object offers neither and can only move forward, so reversed() raises TypeError.

open as a page

How does next((x for x in xs if p(x)), None) find the first match without scanning all of xs?

level: middleimportance: should knowfreq 40%

basics

~20 s

The filtered source is lazy, so next pulls items one at a time and stops the moment the predicate first holds. Nothing beyond the match is examined, and the default supplies a value when no item matches.

open as a page

Why does any() make a side-effecting predicate unsafe over a batch of items?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Short-circuiting makes the number of predicate calls data-dependent: any() stops at the first truthy result, so items after it never run. Effects hidden in the predicate land on an arbitrary prefix, and a retry redoes them.

open as a page

Why is zip(*rows) a risky way to transpose a 2.4 GB inventory feed?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The * unpacking consumes the entire row source into one argument tuple before zip starts, so a lazy 2.4 GB feed is fully materialized. zip then pairs fields by position, and any ragged row silently truncates every column beyond the shortest.

open as a page

Why does a StopIteration raised inside a generator body surface as RuntimeError?

level: seniorimportance: should knowfreq 30%

basics

~20 s

PEP 479 made it an error for StopIteration to escape a generator frame, because it used to end the generator silently and truncate output. Since Python 3.7 the escaping exception is replaced by RuntimeError, chained to the original.

open as a page