Logo
Jay Gurav
6:32:56 PM
7 min read

Functions: Am I pure?

Table of Contents

TL;DR:

Pure functions are a nice idea—clean, predictable, and easy to test. But in the messiness of real-world software, things aren’t always so… pure. This is me trying to untangle what purity means in programming, why functional folks love it, and where it fits (or doesn’t) in the wild.

If you hang around functional programmers long enough, you’ll start to notice something: they really love pure functions. Like, really love them. There’s a kind of quiet reverence for these mythical creatures—functions that touch nothing, change nothing, and simply return a result, neat and tidy.

And honestly? I get it. There’s a kind of elegance to the idea. But I also think that this obsession with purity—what it means, what it gives us—might be part of why functional programming can feel so out of step with how we actually build software.

Because let’s be real: most of what we build in the real world isn’t all that pure.

In this post, I’m going to try and unpack what a function actually is, what it means for one to be “pure,” and why that matters—or doesn’t—depending on what you’re doing.

Functions, Flashbacks & Functional Programming

Remember that one chapter in high school math—the one with functions and sets and domain-to-range mappings? If your brain just went foggy, don’t worry. Here’s the short version:

A function is a way to turn an input into an output. That’s it.

Mathematically, it’s a mapping from a “domain” (input set) to a “co-domain” (output set). Every input gets exactly one output. It’s tidy, predictable, and totally abstract—like a calculator You punch in 2 * 2 and you always get 4. No surprises, no mood swings, no hidden dependencies. Just input → output.

We can represent that kind of function easily in code:

function square(x: number): number {
  return x * x;
}

Here, the square function takes a number and returns its square. That’s it. No surprises. You give it 2, you get 4. You give it 3, you get 9. Every time.

But here’s the thing: real software doesn’t live in a vacuum. It runs in messy, complex environments where things are constantly changing, breaking, mutating, and talking to other things. And that’s where this whole “purity” conversation gets a little weird.

What Does It Mean for a Function to Be “Pure”?

A pure function is one that behaves like that math example above. Same input, same output, no cheating.

But most of the time, our functions aren’t just math—they’re opening files, saving things to databases, talking to APIs, mutating DOM elements, throwing errors, logging things, catching exceptions, and so on.

So let’s slow down and ask: what actually makes a function pure?

1. Isolation

A pure function is totally cut off from the world around it. It only knows what you tell it through its arguments. No peeking at global variables, no reaching into your app state, no relying on the system clock or database. It’s like being blindfolded and asked to solve a puzzle with only the pieces in your hands.

Note:

A pure function is isolated. What it does is entirely based on the inputs you give it.

2. No Side Effects

This is the big one. A side effect is anything a function does besides return a value. Examples include:

  • Changing a variable outside the function
  • Mutating the input
  • Writing to a file or database
  • Printing to the console
  • Throwing an error

In other words, side effects are everything that make a function not just a function—but part of a program that’s alive and doing stuff.

Note:

A pure function doesn’t change the world. It just gives you an answer.

3. Referential Transparency

This is a fancy way of saying: if you replace a function call with its output, nothing breaks.

function double(num: number): number {
  return 2 * num;
}

const x = double(3); // is exactly the same as:
const x = 6;

If you can do that and your program still behaves the same, congrats—your function is referentially transparent.

Note:

Referential transparency means your functions are predictable and interchangeable with their outputs.
function double(num: number): number {
  return 2 * num;
}

const x = double(3); // is exactly the same as:
const x = 6;

If you can do that and your program still behaves the same, congrats—your function is referentially transparent.

Note:

Referential transparency means your functions are predictable and interchangeable with their outputs.

So… What’s the Big Deal? Pure functions are basically the ideal citizen in functional programming land. They’re predictable, testable, composable, and don’t cause trouble. They’re the kind of functions you want to be friends with—quiet, polite, never unexpectedly crash your program.

Here’s the cheat sheet:

  • âś… No side effects
  • âś… Input → Output only
  • âś… Same input = same output (always)

Also, pure functions are idempotent, which means you can call them a million times and get the same result every time. They’re kind of boring that way—in a good way.

Why Testers Love Them One of the best parts about pure functions? They’re a dream to test.

When a function doesn’t rely on anything outside itself, you don’t have to mock a bunch of dependencies or set up elaborate test scaffolding. You just give it an input and check the output.

Note:

A pure function is a self-contained little logic machine—perfect for unit tests.

Since they don’t depend on external state, and always return the same thing for the same input, they’re easy to verify, easy to debug, and hard to break.

Okay, But Where’s the Catch? All of this sounds great. But remember what I said at the start?

There’s not a whole lot of “purity” in most real-world applications.

That’s because most useful programs need to do things. They need to save data, show things to the user, respond to the world. That means side effects. And side effects mean impurity.

So while pure functions are amazing for logic, you still need a way to connect that logic to the messy, unpredictable world.

Functional programming languages often solve this by separating the pure stuff from the impure stuff. You push all the messy, side-effect-laden code to the edges, and keep the core of your app clean and pure. This way, most of your logic can still be composed, tested, and reasoned about—even if the app itself still has to talk to a database eventually.

Final Thoughts

I’m still figuring this all out, to be honest. The more I explore functional programming, the more I appreciate the beauty of pure functions—and the more I realize how rare they are in practice.

But they’re worth striving for. Pure functions make your code easier to read, test, and trust. They let you build systems that are more predictable and less prone to surprising behavior. And even if you can’t go full-functional, just nudging your code in a purer direction can bring real benefits.

So no, not every function has to be pure. But the more you can keep your logic clean, your dependencies explicit, and your code testable—the easier life tends to get.

One last thing:

Purity isn’t about being perfect. It’s about being intentional. About knowing where the side effects live—and keeping them on a short leash.

That’s it for now. I hope this gives you a clearer picture of what pure functions are. Let’s keep learning.

-an article by Jay Gurav.