skip to content

How does Python's map() behave when given several iterables of different lengths?

level: middleimportance: should knowfreq 35%

answer

  1. map takes more than one iterable
  2. One argument per iterable, in lockstep
  3. The shortest input decides the end
  4. No padding, no warning, no strict flag
  5. zip has strict=True; map does not

basics

~20 s

map() takes one item from each iterable per step and calls the function with that many arguments. It stops silently as soon as the shortest iterable runs out, so extra items in the longer ones are never seen.

solid answer

~40 s

`map(func, a, b, c)` calls `func(a_i, b_i, c_i)` for each step, so the callable must accept exactly as many parameters as you pass iterables - otherwise you get a `TypeError` at the first consumed item, not at construction. Iteration stops when **any** input is exhausted, truncating to the shortest, and it does so silently: unlike `zip()`, which gained `strict=True` in Python 3.10 to make mismatched lengths raise, `map()` has no such option on 3.14. So `list(map(pow, [2, 3, 4], [3, 2]))` is `[8, 9]` - the `4` is dropped. `filter()` is different: it takes exactly one predicate and one iterable, never several. If a length mismatch would be a bug in your data, check the lengths yourself or zip strictly first.

code

python · 6 lines
python
print(list(map(pow, [2, 3, 4], [3, 2])))  # [8, 9] - the 4 is dropped

def blend(a, b):
    return f"{a}:{b}"

print(list(map(blend, ["p1", "p2", "p3"], [10, 20])))  # ['p1:10', 'p2:20']

go deeper

for a junior

Know that map() can take more than one iterable and that it then calls your function with one argument from each - and that it quietly stops when the shortest one ends.

for a middle

Explain the lockstep mechanics: arity must match the number of iterables, errors surface on first consumption, truncation is silent, and zip(strict=True) is where strictness lives.

for a senior

Show that you treat silent truncation as a data-integrity risk in parallel-sequence code, assert lengths or zip strictly at the boundary, and know that iterator inputs can be left partially consumed.

for a principal

Own the contract question: decide whether misaligned parallel data should fail loudly at ingest rather than be papered over downstream, and set the team convention that keeps such mismatches out of pipelines.

### The n-ary form of map The signature people remember is `map(function, iterable)`, but the real one is `map(function, *iterables)`. Given more than one iterable, `map` advances a cursor over each of them in lockstep and calls the function once per step with one argument taken from each: ```python list(map(lambda a, b: a + b, [1, 2, 3], [10, 20, 30])) # [11, 22, 33] ``` This is the element-wise combine that people otherwise write as a comprehension over `zip()`. In fact `map(f, a, b)` and `(f(x, y) for x, y in zip(a, b))` produce the same values. ### The arity rule The number of iterables fixes the number of arguments the callable receives. Pass two iterables to a one-parameter function and Python raises `TypeError: <lambda>() takes 1 positional argument but 2 were given`. Crucially that error appears **when the first item is consumed**, not when `map(...)` is evaluated, because `map` does not call the function during construction. A `map` built with the wrong arity looks perfectly healthy until something iterates it. The mirror mistake is passing a function that needs two arguments and a single iterable of pairs. `map(f, [(1, 2), (3, 4)])` calls `f((1, 2))` - one tuple argument, not two - and fails. Unpacking pre-zipped tuples needs either a wrapper that does the unpacking, or the dedicated stdlib combinator for that shape. ### Truncation to the shortest input Iteration ends the moment any one input raises `StopIteration`. There is no padding and no error: ```python list(map(pow, [2, 3, 4], [3, 2])) # [8, 9] ``` `pow(2, 3)` is `8`, `pow(3, 2)` is `9`, and the `4` is never used because the second list ran dry. That silence is a genuine hazard when the two sequences are supposed to be parallel records - a header row list and a values list, say - and one of them lost an element upstream. The result is quietly short rather than loudly wrong. **Note the deliberate contrast with `zip()`.** Python 3.10 added `strict=True` to `zip()` (PEP 618) precisely so that mismatched parallel sequences raise `ValueError` instead of truncating. `map()` received no equivalent, and still has none on 3.14. If strictness matters, the practical route is `map(func, *zip(*pairs))`-style restructuring, or more simply a comprehension over `zip(a, b, strict=True)`, which gets you both the strictness and the readability. Python 2's `map` had the opposite behaviour again: it padded the shorter inputs with `None` and returned a list as long as the longest input. Anyone carrying that memory across will read modern truncation as a bug. ### Consuming side effects on partially-read inputs Because `map` pulls one item from each source per step, the longer sources are left partially consumed if they are themselves iterators rather than lists. After the map is exhausted, an input iterator may have had one extra item pulled from it - the pull that produced the value discarded when a sibling ran out. If those inputs are file handles or generators you intend to keep reading, that lost item is a real defect. With plain lists it does not arise, since a list can be re-iterated from the start. ### Where the multi-iterable form is genuinely worth it It reads well when the callable is an existing named function and the intent is "combine these two parallel sequences element by element": merging a list of keys with a list of values, applying a per-record formatter, or comparing two runs field by field. The moment you find yourself writing `map(lambda a, b: ...)`, a comprehension over `zip(a, b)` names both elements and is easier to read - and with `strict=True` it also catches the length mismatch. ### The filter contrast `filter` has no n-ary form at all. Its signature is `filter(function_or_None, iterable)` - exactly one predicate and exactly one iterable - and passing a second iterable raises `TypeError`. Filtering pairs of parallel sequences therefore means zipping them first and filtering the tuples, then unpacking again if you need the sequences back separately. That asymmetry between `map` and `filter` is itself a small interview probe: candidates who have only ever seen the one-iterable `map` usually assume both take `*iterables`. ### Summary One iterable per parameter, lockstep advance, stop at the shortest, no padding, no strict mode, and every error deferred to the first consumption. If length equality is part of your contract, assert it - `map` will not.

  • How would you make a length mismatch between two parallel sequences raise instead of truncating?
    Use `zip(a, b, strict=True)` - added in Python 3.10 - and iterate that in a comprehension or generator expression, which raises `ValueError` when one input outlives the other. `map()` itself offers no strict mode, so the alternative is an explicit `len(a) == len(b)` assertion before building the pipeline. Checking beforehand only works for sized inputs, not for arbitrary iterators.
  • Why does map(f, [(1, 2), (3, 4)]) fail when f takes two parameters?
    With a single iterable, `map` passes each element as **one** argument, so `f` is called as `f((1, 2))` - one tuple - and raises `TypeError` for the missing second parameter. The fix is either to pass two separate iterables so `map` supplies two arguments, or to wrap `f` in something that unpacks the tuple. Python 3 removed automatic tuple parameter unpacking, so there is no implicit destructuring.
  • If the inputs to a multi-iterable map are generators rather than lists, what happens to the longer one?
    It is left partially consumed. When one source is exhausted, `map` has usually already pulled an item from the earlier sources for that final step, and that item is discarded. A generator or file handle you intended to keep reading has therefore silently lost an element. With lists it never matters, since a list re-iterates from the start.

saying these in an interview costs you the question

  • Thinking map pads short inputs with None
  • Expecting an error on mismatched lengths
  • Believing filter also accepts several iterables
  • Passing a two-argument function one iterable of pairs
  • Assuming the arity error appears at construction

context