Pure function

On this page 7

A pure function is a function whose result depends only on its arguments and whose evaluation produces no observable side effects. Called twice with the same input, it returns the same output, and calling it changes nothing outside its own scope: no global variable, no file, no database row and no screen.

In functional programming, pure functions are the default building block, and languages such as Haskell enforce purity through the type system. Mainstream languages, including JavaScript, Python, Java and C#, do not enforce it, so purity there is a design discipline that developers apply deliberately to make code easier to test, reason about and parallelize.

The two conditions that make a function pure

A function is pure only when it meets both conditions at once. Failing either one makes it impure, even if the code looks harmless.

  1. Determinism: the return value is fully determined by the arguments. The function does not read mutable global state, the system clock, a random number generator, environment variables or any input stream.
  2. No side effects: evaluation does not modify anything observable outside the function. It does not mutate its arguments, write to a log, send a network request, throw an exception that callers rely on for control flow, or change shared memory.

Local mutation does not break purity. A function may create a local array, fill it in a loop and return it, because no caller can observe the intermediate states. What matters is the contract at the boundary, not the style of the code inside.

The table below classifies common operations by whether they keep a function pure.

Operation inside the functionPureReason
Arithmetic on argumentsYesResult depends only on inputs
Building and returning a new listYesNothing outside is changed
Reading a global mutable variableNoOutput depends on hidden state
Calling Date.now() or random()NoOutput varies between calls
Mutating an argument in placeNoCaller’s data changes
Writing to a log or consoleNoObservable side effect
Reading a constant defined onceYesValue can never change
Operations and their effect on purity

Referential transparency and why it matters

Pure functions give expressions referential transparency: any call can be replaced by its result without changing the behavior of the program. If square(4) is pure, every occurrence of it can be replaced with 16. This property lets both people and compilers reason about code through simple substitution, much like algebra.

Several practical benefits follow directly from this property.

BenefitMechanismPractical effect
MemoizationSame input always yields same outputResults can be cached safely
Easy unit testingNo setup of external state neededTests are short and deterministic
Safe parallelismNo shared mutable state to protectNo locks or race conditions
Lazy evaluationEvaluation order cannot change resultsWork can be deferred or skipped
Compiler optimizationCalls can be reordered or removedCommon subexpression elimination
Benefits that follow from purity

These benefits explain why front-end state libraries require reducers to be pure: given the previous state and an action, a reducer must return the next state without mutating the old one. That rule makes features such as time-travel debugging and predictable re-rendering possible.

Purity is often confused with neighboring concepts of functional programming, yet each answers a different question. The comparison below separates them.

ConceptQuestion it answersRelation to purity
Pure functionDoes the result depend only on inputs, with no effects?The property itself
ImmutabilityCan a value change after creation?Makes purity easier to guarantee
IdempotenceDoes repeating a call leave the same end state?Impure operations can be idempotent
Higher-order functionDoes it take or return functions?Can be pure or impure
ClosureDoes it capture variables from its scope?Pure only if captured values never change
Pure functions and neighboring concepts

The difference between purity and idempotence is a classic source of error. An HTTP PUT that writes a record is idempotent, because repeating it leaves the same state, but it is clearly impure because it changes a database.

Handling side effects in a pure design

Real programs must read input, write files and talk to networks, so no useful application is entirely pure. The standard architecture is a functional core with an imperative shell: business rules live in pure functions, while a thin outer layer performs input and output and passes plain data to the core.

  • Pass dependencies as arguments: give the function the current time or a random seed instead of letting it read them.
  • Return new values rather than mutating inputs, using spread syntax, copy methods or persistent data structures.
  • Describe effects as data: return a value that says what should be written, and let the shell perform the write.
  • Isolate effects in types: Haskell marks effectful code with the IO type, so the compiler rejects hidden effects in pure code.

This separation keeps the largest and most complex part of the code testable without mocks, while the effectful shell stays small enough to verify with a few integration tests.

Limitations and common misconceptions

Purity has costs as well as benefits. Returning new data structures instead of mutating existing ones can increase memory allocation and garbage-collection work, although persistent data structures with structural sharing reduce this overhead considerably.

  • A function without a return statement is not automatically pure; if it returns nothing, it exists only for its side effects.
  • Exceptions complicate purity: many practitioners treat a thrown exception as an effect and prefer returning result types such as Either or Result.
  • Logging during debugging silently makes a function impure, which matters when a framework relies on purity for caching.
  • Purity is not the same as speed: memoization helps only when calls repeat and the computation costs more than the cache lookup.

Strictly speaking, every computation consumes time, memory and energy, so purity is defined relative to observable effects at the language level, not to the physical behavior of the hardware.

A plain-language summary

A pure function works like a reliable calculator button. Press the square root key with 9 and the answer is always 3, no matter the time of day or what was calculated before. The button also does not quietly change anything else, such as the numbers stored in memory.

Code written this way is easy to trust: the inputs tell the whole story, so checking it, reusing it and running many copies at once becomes much simpler.

An example from practice

A team building an online store first wrote a price function that read the current date and a global tax setting, then updated the cart total directly. Its tests failed unpredictably around midnight and whenever another test changed the tax setting.

The team rewrote it as a pure function, calculateTotal(items, taxRate, date), which returns a new total and changes nothing. A cart with items worth 80.00 dollars, a 20% tax rate and a 10% promotional discount now always produces 86.40 dollars: 80.00 minus 8.00 is 72.00, and 72.00 plus 14.40 in tax gives 86.40. The intermittent test failures disappeared, the team added more than 200 fast table-driven test cases, and the checkout service began caching totals for unchanged carts safely.