Before we talk about reducers and get into better state management practices for serious JS apps, we need to take a side step to grasp one concept about functions.

Pure function definition

What are pure functions?

A pure function is a function which:

  • Given the same input, will always return the same output.
  • Produces no side effects.

Let's touch on each of these points one by one. First, that for a function be pure, it needs always return the same output for a given input.

Basic example of pure function

Let's craft a very basic example. Let's make a function that adds two to every number it receives. We'll call it addTwo:

const addTwo = (num) => num + 2;

Pretty straightforward right? We pass in a number, the function returns a value incremented by 2. So for example, if we pass in 1. We know we're always going to get three:

const result1 = addTwo(1);
const result2 = addTwo(1);
const result3 = addTwo(1);
console.log(result1, result2, result3); // 3 3 3

Benefit of pure functions--predictable

And naturally, that's what we always get. We wouldn't expect that for a given input, 1, we would have any other output than 3. And this is regardless of the context, for example, where this function is used in our application. None of the conditions around using the function are going to change the output it returns. In other words, it is utterly predictable. That is the first condition of pure functions. They are predictable.

Impure if relies on shared mutable state

So what if we modified this function to where instead of passing in the number that we wanted to add by 2, we put it in it's own variable outside the scope of the function and made it 1?

let num = 1;

const addTwo = () => num + 2;

addTwo(); // 3

It may seems like this works at first. But what happens if someone is also working on our program and happens to reassign the value that num is set to, say to the string 1?

let num = 1;

num = "1";

const addTwo = () => num + 2;

addTwo(); // '12'

It breaks our function. In this case, we have an impure function because we see that it relys on mutable, that is changeable, shared state. The problem is that the num variable is longer scoped to the function and therefore out of our control. We can call the function the exact same way, but get two completely different answers. It's not longer predictable. And to make sure we get the right result we have to keep looking outside of our function, so we can't just focus on calling our function, we have to pay attention to what's going on outside it, which is a lot more work.

Pure functions must always product the same output, given a certain input

Let's take a bit of code from the last lesson, the getDate function, which just created a new date with the date constructor.

function getDate() {
  const date = newDate();
  return date;
}

So it looks good at first. It's not relying on any data, we don't have to pass in any arguments, so it's always going to function the same way no matter how it's called. The date variable is not outside in the outer scope, its within the lexical environment of the function, so we're not running into any problems with shared mutable state. So what do you think is this a pure function or not and why?

Let's call it a few times again and see:

function getDate() {
  const date = new Date();
  return date;
}

console.log(`
  date1: ${getDate()}
  date2: ${getDate()}
  date3: ${getDate()}
`);

Note also that we can use this neat formatting trick with the power of template literals to be able to easily read these logged values on individual lines.

So it looks like yes! We get all exact same string when we call getDate three times. But just wait a second and call this function again. Oops. That's a totally different result. So while it has many of the features of a pure function, ultimately it is impure because we get a different output over time, even if called the same way. This is a small distinction, but functions are pure only if given the same input, it will always product the same output.

So now, we understand the first criteria of pure functions, for a certain input, the same output, always. What was the second part? That a pure function must produce no side effects.

Okay, now we've got to ask--what in the world is a side effect?

Definition of a side effect

A side effect simply means something that changes the external state of our application. When we're dealing with pure functions, this means any data outside of our function. In other words, our function, while it's doing it's thing can't change something outside of it.

Here are some examples of side effects, that we've come very accustomed to doing already:

  • Modifying a variables in any parent scope
  • console.log
  • Working with the DOM or other browser APIs
  • Making a network request to get or change data
  • Calling other functions that perform side-effects

There are tons more examples, but they are all encapsulated in the idea--side effects are any time we interact with the outside world.

Side effects are mostly avoided in functional programming, which makes the effects of a program easier to extend, refactor, debug, test, and maintain. This is the reason that most frameworks encourage users to manage state and component rendering in separate, loosely coupled modules.

Let's take a look at a more complex example. In past, we looked at how a user of our app might want to add a product to their favorites. Let's recreate that function here, where favorites is an array:

const favorites = [];

function handleFavoriteProduct(favorite) {
  favorites.push(favorite);
}

I recommend that you favor pure functions. Meaning, if it is practical to implement a program requirement using pure functions, you should use them over other options. Pure functions take some input and return some output based on that input. They are the simplest reusable building blocks of code in a program. Perhaps the most important design principle in computer science is KISS (Keep It Simple, Stupid). I prefer “Keep It Stupid Simple”. Pure functions are stupid simple in the best possible way.

Pure functions have many beneficial properties, and form the foundation of functional programming. Pure functions are completely independent of outside state, and as such, they are immune to entire classes of bugs that have to do with shared mutable state. Their independent nature also makes them great candidates for parallel processing across many CPUs, and across entire distributed computing clusters, which makes them essential for many types of scientific and resource-intensive computing tasks.

Pure functions are also extremely independent — easy to move around, refactor, and reorganize in your code, making your programs more flexible and adaptable to future changes.

A pure function must not rely on any external mutable state, because it would no longer be deterministic or referentially transparent.

Is JavaScript pure?

In some so-called functional language, purity is enforced by the language and side-effects are not allowed.

In JavaScript, however, purity must be achieved through effort.

JavaScript’s mutability can be a disadvantage, but we can better manage these mutable data structures through language features we've already learned such as the spread operator, as well as pursuing pure functions.

Apps depend on side effects

So note that we're not going to be changing all of our functions to pure functions. That we would be excessive and unnecessary. And ultimately it’s impossible to create most useful modern applications without effects. For example, we couldn't make use APIs with side effects. What's important here is grasping the value of having reliable pure functions which are in the end easier to manage and test for bugs as well as the benefit of being aware and preventing side effects when possible.