A pure function follows exactly two rules: given the same input, it always returns the same output, and it never causes a side effect anywhere else in the program. No reading a global variable, no writing to one, no network call, no mutating an argument, no console output. That definition sounds narrow, but functions that follow it are disproportionately easy to test, cache, and reason about — which is why they sit at the center of functional-style programming.
What changed in 2026
- Linters catch impurity earlier. More static analysis tooling can flag likely side effects — mutated arguments, reads from outer scope — directly in the editor, rather than surfacing them as a bug report weeks later.
- AI code generation makes purity easier to request explicitly. Prompting an assistant for "a pure function that does X" is now a common, well-understood pattern, and most current models produce genuinely side-effect-free code when asked directly.
- Memoization libraries got easier to drop in. Caching a pure function's results now takes a single utility call in most ecosystems, rather than a hand-rolled cache object, which raised the payoff of writing functions purely.
The precise definition, with real code
// Impure — depends on outside state, so the result can change across calls
let taxRate = 0.08;
function priceWithTax(price) {
return price + price * taxRate;
}
// Pure — everything the function needs comes in as an argument
function priceWithTax(price, taxRate) {
return price + price * taxRate;
}
// Impure — mutates the argument it was given
function addItem(cart, item) {
cart.push(item);
return cart;
}
// Pure — returns a new array; the original is untouched
function addItem(cart, item) {
return [...cart, item];
}
The second version of each function is testable with nothing but plain values — call it, check the return value, done. The first version requires tracking and resetting whatever outside state it touches.
Referential transparency
The formal name for "same input, same output, no side effects" is referential transparency: a call to a pure function can be replaced by its return value anywhere in the code without changing what the program does. This is what allows a compiler, or a human reading the code, to reorder, cache, or skip repeated calls safely. Impure functions cannot be treated this way — calling one twice might behave differently, or matter for reasons beyond its return value.
Pure vs impure, side by side
| Property |
Pure function |
Impure function |
| Output depends only on arguments |
Yes |
Not guaranteed |
| Safe to cache (memoize) |
Yes |
Only with care, if ever |
| Testable without mocks |
Yes |
Often needs setup or mocking |
| Safe to call from multiple threads |
Yes |
Needs synchronization if state is shared |
| Can log, save, or fetch |
No |
Yes, that is the point of many of them |
Why total purity is not the realistic goal
Every useful program eventually reads a file, calls an API, or writes to a database — all inherently impure. The practical goal is not zero impure functions; it is pushing impure parts to the outer edges of the program (I/O, logging, randomness) while keeping the bulk of the logic — calculations, transformations, validations — pure and testable in isolation.
How to spot an impure function hiding as pure
Watch for functions that look clean but quietly break one of the two rules: reading Date.now() or Math.random() internally, reading a module-level variable instead of taking it as a parameter, or mutating an object passed in even though the return value looks fine. None of these throw an error — they just make the function behave differently across calls, which defeats the point of treating it as pure.
Common mistakes
Assuming a quick test with no visible change means pure. A function that mutates its argument can pass a shallow test while still being impure and dangerous to call twice with the same object.
Forgetting that pure functions can still throw. Throwing for genuinely invalid input is fine; the purity rule is about outside state and side effects, not about never raising an error.
Over-purifying code that genuinely needs to be stateful. Not everything benefits from being forced pure — an object managing a live connection is legitimately stateful, and fighting that is often more effort than it is worth.
FAQ
Can a pure function call another pure function?
Yes, as long as every function in the chain follows both rules. Composing pure functions is one of the main ways functional-style code is built.
Is a pure function the same as a static method?
No. A static method can still read or mutate shared state; purity is about behavior, not where the function is defined.
Do pure functions make code automatically fast?
Not automatically, but they enable optimizations — memoization especially — that are unsafe to apply to impure code.
Can a pure function use immutable data internally?
It should. Mutating a local variable that never leaves the function is technically fine, but working with immutable data throughout tends to make purity easier to maintain as a function grows.
Where to go next