Functional Programming Patterns
Pure functions, immutability, and composable pipelines with functools and itertools
functools, itertools, comprehensions and generators. Used deliberately, these produce code that is easier to test, cache and reason about — used blindly, they produce unreadable one-liners. This topic is about applying FP where it pays off.
- define a pure function and explain referential transparency,
- choose immutable structures (
tuple,frozenset, frozendataclass) and avoid shared-mutable-state bugs, - reach for the right
functoolstool:reduce,partial,lru_cache,singledispatch, - stream data lazily with
itertools(chain,islice,groupby,accumulate,takewhile), - compose small transformations into a readable pipeline,
- state honestly when FP hurts readability in Python.
1. Pure functions and referential transparency
A function is pure when it satisfies two rules: (1) its return value depends only on its arguments, and (2) it causes no observable side effects — no mutating of external state, no I/O, no clock or random reads. The same inputs always yield the same output.
# PURE: output depends only on inputs, nothing external changes
def add_tax(price: float, rate: float) -> float:
return round(price * (1 + rate), 2)
# IMPURE: reads a global, mutates a global, and does I/O
TOTAL = 0.0
def add_tax_bad(price: float) -> float:
global TOTAL
result = price * 1.2
TOTAL += result # side effect: mutates external state
print(result) # side effect: I/O
return result
Referential transparency means any call to a pure function can be replaced by its result without changing the program. add_tax(100, 0.2) is interchangeable with 120.0 everywhere. That property is what makes pure functions trivially testable (no fixtures, no mocks), safe to cache, and safe to run in parallel — there is no shared state to corrupt.