Pure Functions
16 min
A pure function always produces the same output for the same input, and doesn't change anything outside itself — no mutating an argument, no printing, no reading a global variable, no network/file access. Compare:
# impure -- mutates the argument, returns None
def add_impure(lst, item):
lst.append(item)
# pure -- returns a new list, leaves the original untouched
def add_pure(lst, item):
return lst + [item]
Both "add an item" — but add_pure is far easier to reason about: call
it as many times as you want, in any order, from anywhere, and it will
never surprise you by having changed something you didn't expect.
original is provably untouched -- that's the whole guarantee purity gives you. An impure version using .append() would have silently changed 'original' too, which is often not what you want.
Purity matters most once code gets complex: a pure function is trivially testable (same input always gives the same output, no setup needed), and safe to run in parallel (no shared mutable state means no race conditions) or cache (same inputs always give the same answer, so the result can be reused). Most real programs mix pure and impure code — the functional-programming discipline is pushing as much logic as possible into pure functions, and keeping the unavoidable impure parts (reading a file, printing output, mutating a database) as a thin layer around them.
Write `add_pure(lst, item)`: return a NEW list with `item` appended, WITHOUT mutating `lst` (unlike `lst.append(item)`, which mutates in place and returns None).
Write `total_pure(prices, tax_rate)`: return the total of `prices` with tax applied (`sum(prices) * (1 + tax_rate)`), rounded to 2 decimals. No mutation, no printing, no reading external state — just input in, output out.
What makes a function 'pure'?
You can write pure functions that avoid mutating their inputs, and understand why purity makes code easier to test, reason about, and run safely.