Pure Functions
A pure function always produces the same output for the same input and causes no side effects — making it predictable, testable, and safe to run anywhere.
★★★★★4/5Inside a codebase — classes, modules, files
How it works
A pure function has two properties: (1) determinism — given the same inputs, it always returns the same output; (2) no side effects — it doesn't mutate external state, write to disk, call an API, log to the console, or interact with anything outside its own scope.
The concept originates from mathematical functions and lambda calculus (Alonzo Church, 1936), and became a programming primitive with Lisp (1958). Languages like Haskell enforce purity at the type level; others leave it as a discipline.
Pure functions are the backbone of functional programming but are broadly useful in any paradigm. A codebase where business logic is composed of pure functions is dramatically easier to test (no setup, no mocks, just input/output assertions), reason about (no hidden state), parallelize (no shared mutable state), and cache (same input = same output = safe to memoize).
The practical discipline: push side effects (database writes, API calls, logging) to the edges of your system. Keep the core logic pure.
Implementation
TypeScript · Go · Rust// ❌ Impure — depends on external state, logs as side effect
let taxRate = 0.07;
function calcTotal(price: number, qty: number): number {
console.log("calculating..."); // side effect
return price * qty * (1 + taxRate); // hidden external dependency
}
// ✓ Pure — deterministic, no side effects, all inputs explicit
function calcSubtotal(price: number, qty: number): number {
return price * qty;
}
function applyTax(subtotal: number, taxRate: number): number {
return subtotal * (1 + taxRate);
}
function formatCurrency(amount: number, symbol = "$"): string {
return `${symbol}${amount.toFixed(2)}`;
}
// Compose pure functions — testable, parallelizable, memoizable
const subtotal = calcSubtotal(29.99, 3); // 89.97
const total = applyTax(subtotal, 0.07); // 96.27
const display = formatCurrency(total); // "$96.27"Why it matters
Impure functions are hard to test (require setting up external state), hard to parallelize (share mutable state), and hard to reason about (output depends on invisible global state). Pure functions eliminate all three problems.
✓ When to use
- →Business rule calculations, transformations, validations — keep these pure
- →Data pipeline stages — each step is a pure transformation
- →Anywhere the logic needs to be unit-tested without mocks
- →Computations that benefit from memoization or caching
✗ When NOT to use
- →I/O is inherently impure — database reads, HTTP calls, logging cannot be pure
- →Some operations require mutation for performance (in-place sort on large data)
Trade-offs
Trivially testable — no setup, no mocks, just call the function
Real systems need side effects — purity must be architected in, not assumed
Safe to parallelize — no shared mutable state
Threading purity through a framework designed around mutations (ActiveRecord, ORM) requires discipline
Referentially transparent — safe to memoize, cache, or inline
Performance-critical code sometimes needs in-place mutation that breaks purity
In production
Reducers must be pure functions: (state, action) => newState. This makes time-travel debugging and hot-reload possible
Functional components and hooks — same props = same output. React's concurrent renderer relies on this property
RDD transformations (map, filter, reduce) are pure — the engine can parallelize and recover them safely
Industry adoption
Related principles
Immutability
Never modify existing data — create a new value instead. Immutable data is safe to share, easy to reason about, and eliminates a whole class of mutation bugs.
Higher-Order Functions
Functions that accept other functions as arguments or return them — enabling composable, reusable abstractions without classes or inheritance.
Separation of Concerns
Divide a program into distinct sections where each section addresses one concern — so changes to one area don't ripple unexpectedly into unrelated areas.
Single Responsibility Principle
A class should have only one reason to change — keep each unit focused on a single job so unrelated concerns don't accidentally break each other.