pure functions
functional programming
software development
computer science
programming concepts

Why is determining if a function is pure difficult?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A pure function is supposed to return the same result for the same input and produce no observable side effects. That sounds simple, but determining purity in real programs is difficult because side effects are often indirect, hidden behind abstractions, or dependent on context outside the function signature.

Purity Is About More Than Not Writing Globals

People often begin with an oversimplified rule such as "a pure function does not modify global state." That is part of the story, but it is not enough.

A function also stops being pure if it depends on anything outside its explicit inputs that can change between calls. That includes:

  • current time
  • random number generators
  • environment variables
  • database state
  • network responses
  • mutable objects passed by reference

So purity is about both outputs and dependencies. The function must be deterministic and isolated from external effects.

Hidden Inputs Make Analysis Hard

A function can look pure at the call site while still depending on hidden state internally.

python
1import time
2
3
4def stamp(message):
5    return f"{message}-{time.time()}"

The function takes one argument and returns a string, but it is not pure because the current time is an implicit input.

The same problem appears with randomness:

python
1import random
2
3
4def roll_plus(x):
5    return x + random.randint(1, 6)

Even though nothing obvious is mutated, the result is not determined only by x.

Side Effects Are Not Always Obvious

Some side effects are easy to spot, such as writing to a file. Others are harder because they occur through mutable objects or indirect calls.

python
def append_value(items, value):
    items.append(value)
    return items

This function changes its argument in place, so it is impure even though the mutation happens through a parameter rather than through a global variable.

The difficulty increases in larger systems where a function calls another function that calls another function. To prove purity, you often need to understand the entire dependency chain, not just the top-level function body.

Language and Runtime Features Complicate the Question

Purity is easier to reason about in languages or code styles that encourage immutability and explicit effects. In imperative languages, aliasing and shared mutable state make that much harder.

For example, if a function receives an object reference, you may need to know:

  • whether the object is mutable
  • whether another part of the program can change it concurrently
  • whether any method call on it performs I/O or mutation internally

At that point, purity analysis becomes a whole-program reasoning problem rather than a local syntax check.

Observation Depends on the Boundary You Care About

Another reason purity is hard is that "no side effects" depends on what counts as observable.

A function that memoizes results in an internal cache may look impure because it mutates internal state. But some discussions treat that mutation as unobservable if callers cannot detect it except through performance.

That means purity is sometimes partly definitional. Are we asking for strict mathematical purity, practical referential transparency, or no user-visible side effects. Different teams use those terms with slightly different boundaries.

Static Analysis Helps, But Only So Far

Linters, type systems, and effect systems can help identify suspicious operations, but most mainstream languages cannot prove purity in the general case. They may catch obvious file writes or mutable global access, yet hidden state, foreign function calls, reflection, and aliasing still limit certainty.

In other words, purity is often something you design for rather than something you perfectly infer afterward.

Common Pitfalls

A common mistake is assuming that a function is pure because it has no explicit print, file write, or global assignment. Hidden reads such as time, randomness, and external state still break purity.

Another mistake is ignoring mutation through parameters. Changing an input object is still a side effect.

People also often forget transitive dependencies. If a function calls another impure function, the caller is impure too.

Finally, teams sometimes use "pure enough" informally without agreeing on the boundary. That can cause confusion in API design and testing discussions.

Summary

  • Purity requires deterministic output and no observable side effects.
  • Hidden dependencies such as time, randomness, and external state make purity hard to judge.
  • Mutation through parameters is still impurity, even without global variables.
  • Real purity analysis often requires reasoning about the whole call chain.
  • In practice, purity is easier to guarantee through design choices than to prove after the fact.

Course illustration
Course illustration

All Rights Reserved.