Functional Programming Patterns in Python
Functional programming in Python is not about writing mathematically pure code—it is about writing code that is easier to reason about. The patterns here help you write smaller, more composable functions that do one thing well.
This guide covers the core functional programming patterns you will use daily in Python codebases.
Higher-Order Functions
A higher-order function either takes a function as an argument or returns a function. Python built-in functions like sorted() use this pattern:
words = ["banana", "apple", "cherry"]
# sorted() takes a key function as argument
sorted(words, key=len) # [apple, banana, cherry]
You can also return functions from functions:
def multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = multiplier(2)
double(5) # 10
This is useful for creating specialized functions from generic ones.
map(), filter(), and reduce()
These three functions form the backbone of functional data transformation in Python.
map() - Transform Each Element
map() applies a function to every element in an iterable:
numbers = [1, 2, 3, 4, 5]
# Square each number
squared = list(map(lambda x: x ** 2, numbers))
# [1, 4, 9, 16, 25]
filter() - Keep What Matches
filter() keeps only elements that satisfy a condition:
numbers = [1, 2, 3, 4, 5, 6]
# Keep only even numbers
evens = list(filter(lambda x: x % 2 == 0, numbers))
# [2, 4, 6]
reduce() - Combine Elements
reduce() from functools combines elements into a single value:
from functools import reduce
numbers = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, numbers)
# 24
Combining These Functions
The real power comes from chaining these functions together:
from functools import reduce
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Square the even numbers, then sum them
result = reduce(
lambda x, y: x + y,
map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, data))
)
# 220
Is this readable? Not really. This is where list comprehensions often win.
Lambda Functions
Lambdas are anonymous functions—useful for short, throwaway operations:
add = lambda x, y: x + y
add(2, 3) # 5
When to use lambdas:
- As arguments to sorted(), map(), filter(), reduce()
- For short operations that do not need a full function definition
When to avoid lambdas:
- When the operation is complex enough to need comments
- When you are repeating the same logic in multiple places
# Good: short, clear
sorted(names, key=lambda x: x.lower())
# Bad: too complex for a lambda
process = lambda x: [i for i in range(x) if i % 2 == 0 and i > 10]
# Just write a real function instead
List, Dict, and Set Comprehensions
Comprehensions are Python native functional approach to building collections. They are often more readable than map() and filter() chains.
List Comprehensions
# Instead of: map(lambda x: x**2, range(10))
squares = [x**2 for x in range(10)]
# Instead of: filter(lambda x: x > 5, numbers)
large_numbers = [x for x in numbers if x > 5]
Dict Comprehensions
words = ["apple", "banana", "cherry"]
# Create word -> length mapping
lengths = {word: len(word) for word in words}
# {apple: 5, banana: 6, cherry: 6}
Set Comprehensions
text = "hello world"
# Unique vowels in text
vowels = {char for char in text if char in aeiou}
# {e, o}
Partial Application with functools.partial
Partial application creates a new function with some arguments pre-filled:
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
square(4) # 16
cube(2) # 8
This is useful when you need to pass a function with fixed arguments to another function, like when using sorted() with a custom key:
from functools import partial
# Create a function that always multiplies by 10
scale_by_10 = partial(map, factor=10)
# Use it with sorted
numbers = [1, 20, 3, 40]
sorted(numbers, key=scale_by_10)
When to Use These Patterns
Functional programming patterns work best when:
- You are transforming data through a series of operations
- The operations are independent—each only depends on its input
- You want to avoid mutating original data
Do not force functional patterns where they do not fit. A simple for loop is often clearer than a chain of map() and reduce(). The goal is readable, maintainable code—not functional purity.
See Also
- List Comprehensions in Python — A deeper dive into Python comprehension syntax
- Python Decorators Explained — Functions that modify other functions
- Understanding Python Generators — Lazy evaluation and iterators in Python