What Is a Pure Function?
A pure function is a function that, given the same inputs, always returns the same output — and produces no observable side effects. That's it. Two rules. But following them consistently transforms how you design and reason about software.
Here's a pure function in Haskell:
add :: Int -> Int -> Int
add x y = x + y
And the same idea in Clojure:
(defn add [x y]
(+ x y))
No matter how many times you call add 3 4, you get 7. No surprises. No hidden state. No dependency on the outside world.
What Are Side Effects?
A side effect is anything a function does beyond computing its return value. Common side effects include:
- Writing to a file or database
- Printing to the console
- Making a network request
- Modifying a global variable or mutable data structure
- Reading the current time or generating a random number
Side effects aren't inherently bad — programs that do nothing useful require them. The problem arises when side effects are hidden inside functions that appear to be pure, making programs harder to reason about and test.
Why Purity Matters
1. Referential Transparency
A pure function call can be replaced by its return value anywhere in the code without changing program behavior. This property — called referential transparency — enables compilers to optimize aggressively and developers to refactor safely.
2. Easy Testing
Pure functions require no mocks, no stubs, no database setup. You call the function with inputs and assert on the output. That's the entire test.
3. Parallelism Without Fear
Pure functions don't share mutable state, so they can be run in parallel without race conditions or locks. This is why functional languages scale so well to multi-core and distributed systems.
4. Memoization
Because a pure function always returns the same result for the same inputs, you can cache results automatically. Languages like Haskell do this transparently.
Separating Pure and Impure Code
In practice, all useful programs have some side effects. The functional approach isn't to eliminate them — it's to isolate them. Push side effects to the edges of your program:
- Read all external data at the start (inputs: files, HTTP requests, user input)
- Process that data with pure functions (the core business logic)
- Write outputs at the end (database writes, HTTP responses, console output)
This "functional core, imperative shell" architecture is practical in any language and dramatically improves testability and maintainability.
Purity in Different Languages
- Haskell — Enforces purity at the type system level. Side effects live in the
IOmonad; pure code cannot call impure code. - Clojure — Encourages purity by convention. Immutable data structures are the default; mutation is possible but explicit.
- Elixir — All data is immutable; side effects happen through message passing between processes.
- JavaScript — No enforcement; discipline required. Libraries like Ramda help encourage a pure style.
Start Small
You don't need to rewrite your entire codebase. Start by identifying functions with hidden side effects, and refactor them to accept their dependencies as parameters. Even 20% more purity in a codebase makes a measurable difference in how easy it is to understand and change.