Pure Functions and Side Effects

JavaScript ES2024 · ✓ verified by execution on 2026-08-16

A pure function always evaluates to the same result value given the same argument values and has no observable side effects.

The Anatomy of Purity

To understand why purity matters, you first need to consider what happens when a program runs. Every application relies on some form of state—data stored in memory, databases, or the user interface. When code behaves unpredictably, it is almost always because the state changed when you did not expect it to.

If you pass the same arguments to a pure function, you will always get the exact same answer back. It does not depend on anything that changes over time, like Math.random(). It does not secretly rely on a variable declared elsewhere in your file. It acts as an isolated, mathematical box.

javascript
function add(a, b) {
  return a + b;
}
console.log(add(2, 3));
console.log(add(2, 3));
Output
5
5

If a function uses randomness, it returns a different value each time. That unpredictability makes it impure.

Predict the output javascript

Read the code. What exactly will it print? Commit to an answer before you look.

function generateId() {
  return Math.random();
}
const id1 = generateId();
const id2 = generateId();
console.log(id1 === id2);
Output
false

What exactly is a side effect?

A side effect is any state change that is observable outside the called function other than its return value. A function might return the correct value, but if it quietly modifies something else along the way, it is impure.

Consider a function that updates a global variable. A reader might expect the function merely to compute a value, but instead, it alters the program’s broader state. This creates hidden dependencies.

javascript · visualize
let total = 0;
function addToTotal(amount) {
  total += amount;
  return total;
}
console.log(addToTotal(5));
console.log(addToTotal(5));

Mutating an object argument is another common side effect. Because objects are passed by reference, any changes you make to properties on that object are instantly visible to the code that called your function. This can lead to horrifying bugs where data seems to change itself from a distance.

Memory Mutated
An impure function reaches outside itself and mutates shared memory, so its effect outlives the call and the same inputs no longer guarantee the same result.
javascript
function addAge(person) {
  person.age = 30;
  return person;
}
const user = { name: 'Alice' };
addAge(user);
console.log(user.age);
Output
30

Embracing Immutability

To avoid side effects when working with objects and arrays, you can use immutability. Instead of modifying an object, you create a brand new one. The spread syntax (...) allows you to copy all existing properties into a new object and overwrite just the ones you need to change.

javascript
function addAgePure(person) {
  return { ...person, age: 30 };
}
const user2 = { name: 'Bob' };
const updated = addAgePure(user2);
console.log(user2.age);
console.log(updated.age);
Output
undefined
30

Array methods like .map() and .filter() are fantastic for writing pure functions because they return a new array instead of modifying the original one.

javascript
const nums = [1, 2, 3];
const doubled = nums.map(n => n * 2);
console.log(nums);
console.log(doubled);
Output
[ 1, 2, 3 ]
[ 2, 4, 6 ]

Edge Cases and Limits

Purity is stricter than it first looks in some places and looser in others.

A Note on React and Frameworks

If you are learning JavaScript to use a framework like React, you will hear a lot about pure functions. React relies heavily on functional purity for its components and state updates (for instance, treating props and state as immutable). However, it is important to remember that purity is a core computer science and vanilla JavaScript concept, not a React invention. React merely uses these same vanilla JS mechanics to figure out when to efficiently redraw the screen.

Check yourself

Does calling `console.log` inside a function violate its purity?

Reveal answer

Using console.log doesn't affect purity because it doesn't change variables. — console.log writes to the standard output stream, which is an observable side effect. Therefore, a function calling console.log is technically impure.

If a function does not accept any arguments, is it automatically considered pure?

Reveal answer

No, because it must rely on global state or randomness to produce varying results. — Functions without arguments often rely on global variables or randomness (like Date.now()), making them impure.

Is it acceptable for a pure function to modify an array passed to it, provided it returns the correct mathematical result?

Reveal answer

Only if the array contains numbers. — Modifying an argument (like pushing to an array or mutating an object property) affects the caller's state, which is a side effect.

When copying an object to avoid mutations, does spread syntax (`...`) completely isolate all nested properties?

Reveal answer

Yes, it creates a full, deep clone of the object. — They only make shallow copies. Nested objects are still passed by reference and mutating them inside a function is a side effect.

Challenges

🐞 Bug Hunt +20 XP

The function `addItem` currently modifies the original `cart` array, making it impure. Refactor it into a pure function that returns a new array with the new item added, leaving the original array unchanged.

This code runs. It just does the wrong thing. Read it, find the defect, fix it — the tests below decide when you are right.

javascript
  • Test 1 — expects "Pure!"
Need a hint? (−25% XP)

Use the spread operator `[...array]` to create a copy of the array instead of using `.push()`.

Show solution (0 XP)
function addItem(cart, item) {
  return [...cart, item];
}
const myCart = ['apple'];
const newCart = addItem(myCart, 'banana');
console.log(myCart.length === 1 ? 'Pure!' : 'Impure!');

Challenge 2 +20 XP

Write a pure function `calculateTax` that takes `price` and `taxRate` as arguments and returns the total amount. Do not rely on any global variables.

javascript
  • Test 1 — expects "120"
Need a hint? (−25% XP)

Pass the tax rate as a second argument rather than accessing the global `defaultRate`.

Show solution (0 XP)
function calculateTax(price, taxRate) {
  return price + (price * taxRate);
}
console.log(calculateTax(100, 0.2));