Three loop patterns appear so often that Python gives them names. You have a list and you want every item transformed: that is map. You want only the items that pass a test: that is filter. You want to collapse the whole list into one value — a sum, a product, a maximum: that is reduce.
Each of these is a higher-order function. You supply the data and the behaviour; the tool supplies the loop. Used well, they make intent explicit: map(len, words) says get the length of every word more clearly than a four-line loop. Used poorly, they obscure simple logic behind unnecessary abstraction. This lesson teaches you all three, then shows you when to reach for a comprehension or a plain loop instead.
flowchart LR I["[1, 2, 3]"] --> M["map(double, items)"] M --> O["[2, 4, 6]"] style M fill:#3776ab,color:#fff
map: transform every item
map(function, iterable) calls the function on each element and yields the results, one by one, in order. In Python 3 it returns an iterator, not a list, so if you want to print it or index it you must convert it with list().
map pairs naturally with lambda for small, throwaway transformations, but you can pass any function — built-in, named, or nested. A lambda is just an anonymous function written inline: lambda x: x * 2 takes one argument and returns its double. The syntax is limited to a single expression, which is both a strength (concise) and a weakness (no statements).
words = ['apple', 'pie', 'custard']
# map returns an iterator; list() materialises it
lengths = list(map(len, words))
print(lengths)
squares = list(map(lambda n: n * n, [1, 2, 3, 4]))
print(squares)
def shout(s):
return s.upper() + '!'
print(list(map(shout, words)))
filter: keep only what passes
filter(function, iterable) keeps the items for which the function returns a truthy value. Like map, it returns an iterator in Python 3. The function should return True or False; anything truthy or falsy works, but returning explicit booleans is clearest.
A common trick is to pass None as the function. filter(None, items) keeps only the truthy items, dropping empty strings, zeros, and None values in one go. This is the functional equivalent of a list-comprehension filter, and it is often faster to read once you recognise the idiom.
flowchart LR I["[1, 2, 3, 4]"] --> F["filter(is_even, items)"] F --> O["[2, 4]"] style F fill:#3776ab,color:#fff
nums = [0, 1, 2, 3, 4, 5] evens = list(filter(lambda n: n % 2 == 0, nums)) print(evens) mixed = [0, 'hi', '', None, 42, []] truthy = list(filter(None, mixed)) print(truthy)
reduce: collapse to one value
reduce lives in the functools module, not the built-in namespace. It takes a function of two arguments and an iterable, then walks through the iterable left to right, combining each item with the running result.
reduce(add, [1, 2, 3, 4]) computes ((1 + 2) + 3) + 4, which is 10. The first call uses the first two items; after that, the accumulated result is paired with the next item. You can also supply an initial value that sits before the first item. Providing an initial value is safer because it handles empty iterables gracefully; without one, reduce raises a TypeError on an empty sequence.
flowchart LR A["1 + 2 = 3"] --> B["3 + 3 = 6"] B --> C["6 + 4 = 10"] C --> D["final result"] style B fill:#3776ab,color:#fff
from functools import reduce
nums = [1, 2, 3, 4]
total = reduce(lambda a, b: a + b, nums)
print('sum:', total)
product = reduce(lambda a, b: a * b, nums, 1)
print('product:', product)
joined = reduce(lambda a, b: a + '-' + b, ['a', 'b', 'c'])
print('joined:', joined)
When to use what
map and filter are often clearer as comprehensions. [n * n for n in nums] does the same job as map(lambda n: n * n, nums) and is usually more readable, especially when the transformation is simple. reduce has no comprehension equivalent, but many reduces are already built-ins: sum, min, max, all, any, and str.join. Reach for reduce only when the built-in does not exist.
The real value of learning these three is not the syntax — it is recognising the pattern. When you see a loop that transforms every item, think map. When you see a loop that appends conditionally, think filter. When you see a loop that accumulates a single result, think reduce. Naming the pattern is the first step toward replacing it with the right tool.
Performance and readability trade-offs
For small lists, the choice between map, comprehensions, and loops is mostly a matter of taste. For very large datasets, map and filter can be more memory-efficient because they produce values lazily, one at a time, rather than building an entire list in memory. However, if you immediately wrap them in list(), that advantage disappears.
Readability should be your primary guide. Python's philosophy prefers explicit over implicit. If a comprehension makes the intent obvious, use it. If map with a named function makes the intent clearer, use that. If the logic is complex enough to need multiple statements, a plain for loop is the right choice.
Use map to return a list of the lengths of every word in words. Do not write a loop.
def word_lengths(words):
# use map and list
pass
map(len, words) gives an iterator.
Wrap it in list() to return a list.
def word_lengths(words):
return list(map(len, words))
What does this print? Watch for the iterator trap.
nums = [1, 2, 3] m = map(lambda n: n * 2, nums) print(list(m)) print(list(m))
map returns an iterator, not a list.
Once an iterator is exhausted, re-listing it yields nothing.
Use filter to return a list of only the positive numbers from nums. Numbers greater than zero are positive.
def positives(nums):
# use filter and list
pass
filter(lambda n: n > 0, nums) keeps positives.
Wrap in list() to return a list.
def positives(nums):
return list(filter(lambda n: n > 0, nums))
This function tries to return the maximum value using reduce, but it crashes on an empty list. Fix it by providing an appropriate initial value so reduce has something to start with.
from functools import reduce
def max_value(nums):
return reduce(lambda a, b: a if a > b else b, nums)
Add a third argument to reduce: an initial value.
float('-inf') is smaller than any real number, so any actual value replaces it.
from functools import reduce
def max_value(nums):
return reduce(lambda a, b: a if a > b else b, nums, float('-inf'))
Use reduce to compute the product of all numbers in nums. Start with an initial value of 1. Return 1 for an empty list.
from functools import reduce
def product(nums):
# use reduce with initial value 1
pass
reduce(lambda a, b: a * b, nums, 1) starts with 1.
The initial value means an empty list returns 1, not an error.
from functools import reduce
def product(nums):
return reduce(lambda a, b: a * b, nums, 1)
Recap
map(fn, items)transforms every item; wrap inlist()to materialise.filter(fn, items)keeps only items wherefn(item)is truthy.reduce(fn, items, init)folds items left to right into a single value.- Many reduces already exist as built-ins:
sum,max,all,str.join. - Comprehensions often beat
mapandfilterfor readability; learn both and choose by clarity. - Iterators are single-use: once exhausted, they yield nothing until recreated.
Next you will see how nested functions can remember values from the scope where they were created, a mechanism called a closure.