Pure vs Impure Functions in JavaScript Explained

A pure function always returns the same output for the same input and has no side effects. Learn the difference, why purity matters, and how to spot impure code.

6 min read

A pure function has two simple rules: given the same inputs, it always returns the same output, and it causes no side effects. If a function violates either rule, it is impure.

Here is the difference in one example:

javascriptjavascript
// Pure: always returns the same result for the same inputs
function add(a, b) {
  return a + b;
}
 
// Impure: depends on external state that can change
let taxRate = 0.08;
function addTax(price) {
  return price * (1 + taxRate);
}

Calling add with 2 and 3 always returns 5. You could call it a thousand times and it never varies. Calling addTax with 100 depends on the current value of taxRate, so if taxRate changes between calls, the result changes even with the same argument.

The Two Rules of Pure Functions

Pure function rules

Both conditions must be met. If either fails, the function is impure. Let us look at each rule in detail.

Rule 1: Same Input, Same Output

A pure function's output is determined entirely by its inputs:

javascriptjavascript
// Pure
function square(n) {
  return n * n;
}

Calling square with 4 always returns 16, no matter what else is happening in the program. Compare that with two functions that break the rule in different ways:

javascriptjavascript
// Impure: depends on an external variable
let multiplier = 2;
function scale(n) {
  return n * multiplier;
}
 
// Impure: returns a different value each call
function randomBetween(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
}

Calling scale with 4 depends on the multiplier variable, so the result changes if that variable changes. Calling randomBetween with 1 and 10 returns something different every call by design. Only square is pure.

Rule 2: No Side Effects

A side effect is any observable change to the world outside the function:

  • Modifying an external variable or object property
  • Mutating a passed-in array or object argument
  • Writing to the DOM
  • Making a network request
  • Writing to console.log or a file
  • Changing a database
javascriptjavascript
// Impure: mutates the input array
function addToArray(arr, item) {
  arr.push(item);
  return arr;
}
 
// Pure: returns a new array, leaves the original untouched
function addToArray(arr, item) {
  return [...arr, item];
}

The first version modifies the original array. Any code holding a reference to that array sees the change. The second version creates a new array, leaving the original intact.

Spotting Impurity: A Quick Test

Ask yourself: if I replaced every call to this function with its return value, would the program behave identically? If yes, the function is pure. This idea is called referential transparency.

javascriptjavascript
// Pure: can be replaced with the result
const area = calculateCircleArea(5);
// Same as writing:
const area = 78.53981633974483;
 
// Impure: cannot be replaced because it also logs
function calculateAndLog(radius) {
  const area = Math.PI * radius * radius;
  console.log(`Area: ${area}`);
  return area;
}

Why Pure Functions Matter

BenefitWhy
PredictableYou know the output from the input. No surprises.
TestableNo need to set up external state. Just call and assert.
CacheableYou can memoize the result. Same input always gives the cached value.
ComposablePure functions snap together. The output of one is the input of another.
ParallelizableNo shared state means no race conditions.
DebuggableYou can reproduce any bug by calling the function with the same arguments.

Testing Pure vs Impure Functions

Testing a pure function is trivial: call it with arguments and check the result, with nothing else to prepare:

javascriptjavascript
function discount(price, percent) {
  return price * (1 - percent / 100);
}
 
console.log(discount(100, 20)); // 80 -- always passes

Testing an impure function that reads from shared state is more work, since the test has to set up that state first and clean it up afterward so it does not leak into other tests:

javascriptjavascript
let currentUser = null;
 
function applyUserDiscount(price) {
  if (!currentUser) return price;
  return price * (1 - currentUser.discount / 100);
}
 
// Test requires setting up global state
currentUser = { name: "Alex", discount: 15 };
console.log(applyUserDiscount(100)); // 85
currentUser = null; // Clean up
Pure: discountImpure: applyUserDiscount
Setup neededNoneSet currentUser before the test
Cleanup neededNoneReset currentUser after the test
Depends onOnly its argumentsIts argument plus shared global state

The pure function needs only its arguments. The impure function needs currentUser to be set, and you must clean up after the test to avoid affecting other tests.

The Real World: Impurity Is Necessary

A program with no side effects does nothing useful. You cannot update the DOM, fetch data, write files, or respond to user input without impurity.

The goal is not to eliminate impurity but to isolate it, keeping the calculation itself pure while pushing the side effect to a thin, separate layer:

javascriptjavascript
// Core logic: pure
function calculateCartTotal(items, taxRate) {
  return items.reduce((sum, item) => sum + item.price * item.quantity, 0) * (1 + taxRate);
}
 
// Edge: impure (DOM update)
function updateCartDisplay(total) {
  document.getElementById("cart-total").textContent = `$${total.toFixed(2)}`;
}

An orchestrator function ties the two together. It stays impure because it calls the DOM-updating function, but the actual math lives entirely in the pure function above it:

javascriptjavascript
function checkout(items) {
  const total = calculateCartTotal(items, 0.08); // Pure computation
  updateCartDisplay(total);                        // Impure side effect
}

Keep the core logic pure. Push side effects to the edges. This is the functional core, imperative shell pattern.

For practical guidance, see Writing Pure Functions in JS: A Complete Tutorial. For the broader function concepts, see JavaScript Functions Explained from Basic to Advanced Concepts.

Rune AI

Rune AI

Key Insights

  • A pure function always returns the same output for the same input and has no side effects.
  • Side effects include modifying external variables, DOM manipulation, network requests, and logging.
  • Pure functions are easier to test, debug, cache, and compose than impure ones.
  • Not every function should be pure -- side effects are what make programs useful.
  • Isolate side effects at the edges of your program and keep the core logic pure.
RunePowered by Rune AI

Frequently Asked Questions

Can an impure function call a pure function?

Yes. An impure function can use pure functions internally without becoming pure itself. The impurity of the outer function does not affect the inner pure functions.

Are all built-in JavaScript methods pure?

No. Math.random() is impure (different output each call). Math.max() is pure (same inputs, same output). console.log() is impure (it writes to the console, which is a side effect). Array.map() with a pure callback is pure; with an impure callback it becomes impure.

Should I make every function pure?

No. Pure functions are ideal for data transformation and computation. But a program with no side effects does nothing useful -- no DOM updates, no network requests, no file writes. Use pure functions for your logic and isolate side effects at the edges of your program.

Conclusion

Pure functions are predictable, testable, and composable. They always produce the same output for the same input and leave nothing changed after they run. Impure functions are necessary -- they interact with the outside world -- but keeping your core logic pure and isolating side effects at the edges makes your code dramatically easier to reason about.