Predictable and Testable

Pure versus Impure Functions

Todd Motto by Todd Motto Google Developer Expert Icon Google Developer Expert
JavaScript Icon

Learn JavaScript the right way

Premium JavaScript courses to skyrocket your skills to the top.

5x Courses: Basics, Masterclass, HTML5 APIs, DOM, Testing with Jest.

Functions can be considered pure or impure.

Pure functions do not access or modify variables outside of their scope, and they do not cause side effects (such as network requests, logs, state changes).

Any function that does any of the above, would be deemed impure.

First, let’s explore an impure method of calculating the total of our cart:

const cart = [
  { name: 'Lemonade', price: 299 },
  { name: 'Coffee', price: 399 }
];

let total = 0;

function calculateTotal() {
  cart.forEach(item => total += item.price);
}

calculateTotal(); // 😁 698

Why is this impure?

This function breaks the “no state change” rule, as it externally talks and modifies the cart variable.

It also uses Array.prototype.forEach which is infact intended for side effects, as it returns no data to us unlike Array.prototype.map or Array.prototype.reduce.

Not only this, but look what happens if we run the function a few times:

calculateTotal(); // 😁 total = 698
calculateTotal(); // 😲 total = 1396
calculateTotal(); // 😱 total = 2094

Yikes! Each time we call the function, the whole thing executes again and keeps adding to the total variable.

Sure we could “reset” the total each time, but we’d just be building more points of failure.

So, how could we make this function pure?

As a pure function does not access or modify external variables, it will always return the same result. That’s the first objective.

Here’s how we could do that:

function calculateTotal(data) {
  return data.reduce((prev, next) => prev + next.price, 0);
}

Does it modify a total? No, it doesn’t have to. It returns a calculated state instead based on the arguments given:

console.log(calculateTotal(cart));  // 🤑 698
console.log(calculateTotal(cart));  // 🤑 698
console.log(calculateTotal(cart));  // 🤑 698

Excellent. The function is pure because it is given the data, and always returns the same result.

Guess what? That’s super easy to test now and is going to save us debugging things later down the line.

So this gives you an idea of how pure functions can help, so what about another example just to set things in stone?

Here’s an add to cart function:

function addToCart(drink) {
  cart.push(drink);
}

Is it pure? No!

It accesses variables outside of itself, which means their value could change at any time. Therefore it is not predictable, nor would it render the same output each time, as the external variable could change at any time.

To make it pure, we would have to construct a new data structure, so we do not modify any of the arguments passed:

function addToCart(cart, drink) {
  return [...cart, drink];
}

const newCart = addToCart(cart, { name: 'Water', price: 199 });

A nice pure function, it simply takes the cart as the first argument and the new item as the second argument.

There’s a common and easy pattern to pure functions, they likely just perform actions on their arguments and return a new result.

This also creates a new state change that can be tracked. We’re not beginning to discuss the fascinating topic of immutable JavaScript, but we’ll save that for another day.

Before we finish up, I want to mention the term “Referential Transparency”.

There is much debate on the “true meaning” of being referentally transparent, but it’s said that we could replace the expression with its value - and that’d seal the deal.

What does that mean though? It’s easier to explain with code. Our addToCart call can be replaced with it’s output:

function addToCart(cart, drink) {
  return [...cart, drink];
}

const newCart = [...cart, { name: 'Water', price: 199 }];

When we do this, and it has no impact on the execution of the program, it’s deemed referentially transparent.

Before you go, here’s a few more examples of some pure versus impure:

// ✅ pure, algorithm based on parameters
function add(x, y) {
  return x + y;
}

// ✅ pure
function applyDiscount(item, percentage) {
  return { ...item, price: item.price * percentage };
}

// ❌ impure, uses a "console.log()" side effect
function log(message) {
  console.log(message);
}

// ❌ impure, gets the current time
function getTimestamp() {
  return Date.now();
}

// ❌ impure, but looks pure... causes a DOM side effect
function addClass(element, className) {
  element.classList.add(className);
}

// ❌ impure, but looks pure... will not produce the same value each time
function getRandomNumber(element, className) {
  return Math.floor(Math.random());
}

// ❌ impure, async request using external resource - but you knew that!
function getApiData() {
  return fetch('/api').then(res => res.json());
}

I truly hope this has given you some ideas to refine your functions and think more clearly about their roles and responsibilities. I find by thinking pure functions first, you can begin to refactor a lot of your code, remove variables that are scattered around, and write with far more intent - and that’s what functional programming helps deliver.

Instant Invokation Patterns

« Immediately-Invoked Functions (IIFE)

Context Is Key

"this" Keyword In-Depth »