Explainstuff.mebeta
All concepts
Functional Programmingbeginner6 min

Pure Functions

Same input, same output, no surprises — a function so well-behaved you'd trust it with your house keys.

Think about a vending machine. You press B4, you get the same bag of pretzels every single time. It doesn't care what day it is, it doesn't phone a friend, and it doesn't quietly rearrange the other snacks while you're not looking. A pure function is exactly that polite: give it the same input and it always hands back the same output, and it touches absolutely nothing else.

The problem

Most functions we write are sneakier than a vending machine. They read the current time, fetch data over the network, scribble to a database, or quietly change a global variable sitting somewhere else in the program. These hidden behaviors are called side effects, and a function that has them is called impure.

The trouble is that side effects make code unpredictable. Call the same impure function twice and you might get two different answers, because the world changed underneath it. That makes bugs hard to reproduce, tests hard to write, and the whole program hard to reason about — you can never be sure what a line of code really does just by reading it.

An impure function reaches out
hidden reach
input
impure f
different each time
clock
network
global var
Arrows leak out to the clock, the network, and a global — so the same input can give different answers.

Here's an impure function in F#. It looks innocent, but its answer changes every time you run it:

// Impure: depends on the clock, so the output is never the same twice
let greeting () =
    let now = System.DateTime.Now
    sprintf "The time is %s" (now.ToString("HH:mm:ss"))

You can't write a reliable test for this — what would you even check it against? The answer is a moving target.

Watch out

Side effects aren't evil — saving a file or sending an email is the whole point of many programs. The danger is hidden side effects buried inside functions that look like simple calculations. Surprises in code are where bugs love to hide.

How it works

A pure function follows two simple house rules. First, its output depends only on its inputs — same arguments in, same result out, forever. Second, it causes no side effects — it doesn't reach out to change anything in the outside world. Everything it needs comes in through the front door as arguments, and everything it produces goes out the front door as a return value.

Here's the purest little function imaginable:

// Pure: output depends only on a and b, and nothing else is touched
let add a b = a + b

add 2 3   // always 5
add 2 3   // still 5, every single time

No clock, no database, no network. Just an honest answer you can count on.

Same in → same out, always
always same
input
f
output
A pure function is a vending machine: the same input gives the same output and touches nothing else.

The animation above shows the idea in motion: the same input flows into the function box and the same output always comes out the other side. Notice there are no arrows reaching out to a clock, a database, or anything else — the box is sealed off from the outside world. That self-contained quality is the whole secret.

Why purity is wonderful

Once a function is pure, lovely things become true almost for free:

  • Trivially testable — feed in an input, check the output. No fake clocks, no test databases, no elaborate setup.
  • Cacheable — since the same input always gives the same output, you can remember (or memoize) past results and skip the work next time.
  • Safe in parallel — pure functions don't share or change hidden state, so you can run thousands of them at once without them stepping on each other.
  • Easy to reason about — what you read is exactly what happens. No spooky action somewhere else.
Note

Purity is a close cousin of idempotency. Both are about predictable repetition: calling a pure function twice with the same input is harmless because nothing changes outside it. Keeping your data unchangeable — see immutability — makes purity even easier to achieve.

Push effects to the edges

Of course, a program that never does anything is useless — at some point you must read a file, save a record, or show something on screen. The goal isn't to ban side effects, it's to corner them. Keep the messy, effect-having code at the edges of your program (reading input, writing output), and keep the core — your actual logic — pure.

In practice that means doing the impure work first, then handing plain values to a pure function:

// Impure edge: grab the current time once, out here
let now = System.DateTime.Now

// Pure core: takes the time as an argument, easy to test
let greetingFor (time: System.DateTime) =
    sprintf "The time is %s" (time.ToString("HH:mm:ss"))

greetingFor now

Now the logic lives in greetingFor, which is perfectly pure and testable — you can pass it any time you like — while the single impure line stays out at the edge where it's easy to see.

Key takeaways

  • A pure function always returns the same output for the same input and changes nothing else in the world.
  • Side effects — reading the clock, hitting the network, writing to a database, mutating a global — are what make a function impure.
  • Pure functions are a joy to test: feed in an input, check the output, no mocks or setup required.
  • Because they depend on nothing hidden, pure functions are safe to cache (memoize) and safe to run in parallel.
  • You can't be pure everywhere — programs must eventually DO things — so push side effects to the edges and keep the core pure.

Keep going