Home Tutorials Functions

Functions

map() and filter() in Python: Functional Alternatives to Loops

Pyford Notes July 6, 2026 6 min read
Key points
  • map(func, iterable) lazily applies func to every item and yields the results one at a time — wrap it in list() to see everything at once.
  • filter(func, iterable) keeps only the items for which func returns a truthy value, dropping the rest.
  • filter(None, iterable) is a specific, well-known idiom: passing None instead of a function drops every falsy item — empty strings, zeros, None values — without writing a lambda.
  • map()/filter() and an equivalent list comprehension do the same work; the choice between them is almost entirely about which reads more clearly for the specific case.

map(): applying a function across an iterable

map() applies a function to every item of an iterable, producing one transformed result per input item:

prices = [19.99, 5.50, 42.00]
formatted = map(lambda p: f"${p:.2f}", prices)
print(list(formatted))   # ['$19.99', '$5.50', '$42.00']

map() also accepts multiple iterables at once, calling the function with one item from each, stopping as soon as the shortest input is exhausted:

names = ["Ada", "Grace", "Alan"]
scores = [91, 88, 95]
combined = map(lambda n, s: f"{n}: {s}", names, scores)
print(list(combined))   # ['Ada: 91', 'Grace: 88', 'Alan: 95']

This two-iterable form is a direct alternative to zipping the two lists first and then unpacking each pair inside the function — both work, and which is clearer depends on whether the rest of the code also needs the paired tuples from zip() for something else.

Why map() returns an iterator, not a list

In current Python, map() returns a lazy iterator object, not a list — nothing is actually computed until something iterates over it:

result = map(str.upper, ["a", "b", "c"])
print(result)          # <map object at 0x...>
print(list(result))     # ['A', 'B', 'C']
print(list(result))     # [] -- already exhausted, can't iterate twice

This laziness matters for two reasons: it avoids building an intermediate list in memory when the results are only going to be consumed once in a for loop, and it means a map() object, like any other generator-like iterator, can only be consumed once — a second pass over the same object yields nothing, which is a common source of confusion for code that expects list-like reusability.

filter(): keeping only what passes a test

filter() keeps only the items for which a function returns something truthy, discarding the rest:

numbers = [1, -3, 4, -7, 9, 0, -2]
positives = filter(lambda n: n > 0, numbers)
print(list(positives))   # [1, 4, 9]

Like map(), filter() returns a lazy iterator rather than a list, and needs wrapping in list(), iterating with a for loop, or passing to another function that consumes iterables to actually produce values.

The filter(None, iterable) idiom

Passing the literal value None as the function argument, instead of an actual function, is a specific documented idiom: it tells filter() to keep only the items that are themselves truthy, dropping empty strings, zeros, empty lists, and None values in one call:

raw_fields = ["Ada", "", "Grace", None, "Alan", 0, "Katherine"]
cleaned = filter(None, raw_fields)
print(list(cleaned))   # ['Ada', 'Grace', 'Alan', 'Katherine']

This comes up constantly when cleaning data pulled from a form, a CSV row, or a split string where empty or missing entries need to be dropped without writing out lambda x: x or a full if x loop just to express "keep the truthy ones."

map/filter vs list comprehensions

Every use of map() or filter() can be rewritten as a list comprehension, and in Python specifically, the comprehension form is very often considered the more readable of the two once a lambda is involved:

formatted = list(map(lambda p: f"${p:.2f}", prices))
formatted = [f"${p:.2f}" for p in prices]              # same result

positives = list(filter(lambda n: n > 0, numbers))
positives = [n for n in numbers if n > 0]               # same result

The comprehension avoids the extra lambda syntax entirely and reads left-to-right as "build a list of X for each item where condition." map()/filter() tend to read more naturally when the function being applied already exists and has a name — map(str.strip, lines) is arguably clearer than the equivalent comprehension precisely because there's no lambda needed at all. Neither form is uniformly correct; picking whichever reads more clearly for the specific transformation, rather than defaulting to one out of habit, is the actual skill here.