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.
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:
// 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
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:
// 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:
// 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
// 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.
// 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
| Benefit | Why |
|---|---|
| Predictable | You know the output from the input. No surprises. |
| Testable | No need to set up external state. Just call and assert. |
| Cacheable | You can memoize the result. Same input always gives the cached value. |
| Composable | Pure functions snap together. The output of one is the input of another. |
| Parallelizable | No shared state means no race conditions. |
| Debuggable | You 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:
function discount(price, percent) {
return price * (1 - percent / 100);
}
console.log(discount(100, 20)); // 80 -- always passesTesting 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:
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: discount | Impure: applyUserDiscount | |
|---|---|---|
| Setup needed | None | Set currentUser before the test |
| Cleanup needed | None | Reset currentUser after the test |
| Depends on | Only its arguments | Its 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:
// 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:
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
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.
Frequently Asked Questions
Can an impure function call a pure function?
Are all built-in JavaScript methods pure?
Should I make every function pure?
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.
More in this topic
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.
How to Loop Through Arrays Using JS for Loops Guide
Looping through arrays is the most practical use of JavaScript for loops. Learn how to access each element by index, avoid off-by-one errors, and choose between for, for...of, and forEach.