Python has a handful of functional programming tools that let us transform and combine data without writing explicit loops. They take a function and an iterable, and produce a new iterable.
map() — Transform Every Item
map() applies a function to each item in an iterable. It returns a lazy iterator (not a list), so we wrap it in list() when we need the result.
nums = [1, 2, 3, 4, 5]
# Square each number
squared = list(map(lambda x: x ** 2, nums))
# [1, 4, 9, 16, 25]
# Convert strings to ints
str_nums = ["10", "20", "30"]
int_nums = list(map(int, str_nums))
# [10, 20, 30]
# map with multiple iterables
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
# [11, 22, 33]
filter() — Keep Items That Pass a Test
filter() keeps only the items where the function returns a truthy value.
nums = [1, 2, 3, 4, 5, 6, 7, 8]
# Keep even numbers
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4, 6, 8]
# Remove empty strings
words = ["hello", "", "world", "", "python"]
non_empty = list(filter(None, words)) # None removes falsy values
# ['hello', 'world', 'python']
Passing None as the function is a neat trick — it filters out all falsy values.
reduce() — Combine All Items Into One
reduce() isn’t a built-in — we need to import it from functools. It takes the first two items, applies the function, then takes that result with the next item, and so on until there’s a single value left.
from functools import reduce
nums = [1, 2, 3, 4, 5]
# Sum all numbers (1+2=3, 3+3=6, 6+4=10, 10+5=15)
total = reduce(lambda acc, x: acc + x, nums)
# 15
# Find the max value
biggest = reduce(lambda a, b: a if a > b else b, nums)
# 5
# Flatten a list of lists
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda a, b: a + b, nested)
# [1, 2, 3, 4, 5, 6]
Honestly, most of the time we’re better off using sum(), max(), or a loop instead of reduce(). It’s powerful but can be hard to read.
zip() — Combine Iterables in Parallel
zip() takes multiple iterables and pairs up their elements. It stops at the shortest iterable.
names = ["Alice", "Bob", "Charlie"]
scores = [90, 85, 92]
# Pair them up
pairs = list(zip(names, scores))
# [('Alice', 90), ('Bob', 85), ('Charlie', 92)]
# Super useful with dict()
score_map = dict(zip(names, scores))
# {'Alice': 90, 'Bob': 85, 'Charlie': 92}
# Unzip with *
pairs = [("a", 1), ("b", 2), ("c", 3)]
letters, numbers = zip(*pairs)
# letters = ('a', 'b', 'c'), numbers = (1, 2, 3)
If we need to handle iterables of different lengths without truncating, we use zip_longest from itertools.
from itertools import zip_longest
a = [1, 2, 3]
b = ["x", "y"]
list(zip_longest(a, b, fillvalue="-"))
# [(1, 'x'), (2, 'y'), (3, '-')]
enumerate() — Get Index + Value
Not exactly functional programming, but it pairs perfectly with these tools. It gives us the index and value while iterating.
fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry
# Start from a different index
for i, fruit in enumerate(fruits, start=1):
print(f"{i}: {fruit}")
Comprehensions vs map/filter
In most cases, comprehensions are more Pythonic and readable.
# These are equivalent
list(map(lambda x: x ** 2, nums)) # map style
[x ** 2 for x in nums] # comprehension — cleaner
list(filter(lambda x: x > 3, nums)) # filter style
[x for x in nums if x > 3] # comprehension — cleaner
In simple language, map transforms, filter selects, reduce combines, and zip pairs up. But if we can write it as a comprehension, that’s usually the more Pythonic choice.