Illustration by

Making your JavaScript Pure

Once your website or application goes past a small number of lines, it will inevitably contain bugs of some sort. This isn’t specific to JavaScript but is shared by nearly all languages—it’s very tricky, if not impossible, to thoroughly rule out the chance of any bugs in your application. However, that doesn’t mean we can’t take precautions by coding in a way that lessens our vulnerability to bugs.

Article Continues Below

Pure and impure functions#section1

A pure function is defined as one that doesn’t depend on or modify variables outside of its scope. That’s a bit of a mouthful, so let’s dive into some code for a more practical example.

Take this function that calculates whether a user’s mouse is on the left-hand side of a page, and logs true if it is and false otherwise. In reality your function would probably be more complex and do more work, but this example does a great job of demonstrating:


function mouseOnLeftSide(mouseX) {
    return mouseX < window.innerWidth / 2;
}

document.onmousemove = function(e) {
    console.log(mouseOnLeftSide(e.pageX));
};

mouseOnLeftSide() takes an X coordinate and checks to see if it’s less than half the window width—which would place it on the left side. However, mouseOnLeftSide() is not a pure function. We know this because within the body of the function, it refers to a value that it wasn’t explicitly given:


return mouseX < window.innerWidth / 2;

The function is given mouseX, but not window.innerWidth. This means the function is reaching out to access data it wasn’t given, and hence it’s not pure.

The problem with impure functions#section2

You might ask why this is an issue—this piece of code works just fine and does the job expected of it. Imagine that you get a bug report from a user that when the window is less than 500 pixels wide the function is incorrect. How do you test this? You’ve got two options:

  • You could manually test by loading up your browser and moving your mouse around until you’ve found the problem.
  • You could write some unit tests (Rebecca Murphey's Writing Testable JavaScript is a great introduction) to not only track down the bug, but also ensure that it doesn’t happen again.

Keen to have a test in place to avoid this bug recurring, we pick the second option and get writing. Now we face a new problem, though: how do we set up our test correctly? We know we need to set up our test with the window width set to less than 500 pixels, but how? The function relies on window.innerWidth, and making sure that’s at a particular value is going to be a pain.

Benefits of pure functions#section3

Simpler testing#section4

With that issue of how to test in mind, imagine we’d instead written the code like so:


function mouseOnLeftSide(mouseX, windowWidth) {
    return mouseX < windowWidth / 2;
}

document.onmousemove = function(e) {
    console.log(mouseOnLeftSide(e.pageX, window.innerWidth));
};

The key difference here is that mouseOnLeftSide() now takes two arguments: the mouse X position and the window width. This means that mouseOnLeftSide() is now a pure function; all the data it needs it is explicitly given as inputs and it never has to reach out to access any data.

In terms of functionality, it’s identical to our previous example, but we’ve dramatically improved its maintainability and testability. Now we don’t have to hack around to fake window.innerWidth for any tests, but instead just call mouseOnLeftSide() with the exact arguments we need:


mouseOnLeftSide(5, 499) // ensure it works with width < 500

Self-documenting#section5

Besides being easier to test, pure functions have other characteristics that make them worth using whenever possible. By their very nature, pure functions are self-documenting. If you know that a function doesn’t reach out of its scope to get data, you know the only data it can possibly touch is passed in as arguments. Consider the following function definition:


function mouseOnLeftSide(mouseX, windowWidth)

You know that this function deals with two pieces of data, and if the arguments are well named it should be clear what they are. We all have to deal with the pain of revisiting code that’s lain untouched for six months, and being able to regain familiarity with it quickly is a key skill.

Avoiding globals in functions#section6

The problem of global variables is well documented in JavaScript—the language makes it trivial to store data globally where all functions can access it. This is a common source of bugs, too, because anything could have changed the value of a global variable, and hence the function could now behave differently.

An additional property of pure functions is referential transparency. This is a rather complex term with a simple meaning: given the same inputs, the output is always the same. Going back to mouseOnLeftSide, let’s look at the first definition we had:


function mouseOnLeftSide(mouseX) {
    return mouseX < window.innerWidth / 2;
}

This function is not referentially transparent. I could call it with the input 5 multiple times, resize the window between calls, and the result would be different every time. This is a slightly contrived example, but functions that return different values even when their inputs are the same are always harder to work with. Reasoning about them is harder because you can’t guarantee their behavior. For the same reason, testing is trickier, because you don’t have full control over the data the function needs.

On the other hand, our improved mouseOnLeftSide function is referentially transparent because all its data comes from inputs and it never reaches outside itself:


function mouseOnLeftSide(mouseX, windowWidth) {
    return mouseX < windowWidth / 2;
}

You get referential transparency for free when following the rule of declaring all your data as inputs, and by doing this you eliminate an entire class of bugs around side effects and functions acting unexpectedly. If you have full control over the data, you can hunt down and replicate bugs much more quickly and reliably without chancing the lottery of global variables that could interfere.

Choosing which functions to make pure#section7

It’s impossible to have pure functions consistently—there will always be a time when you need to reach out and fetch data, the most common example of which is reaching into the DOM to grab a specific element to interact with. It’s a fact of JavaScript that you’ll have to do this, and you shouldn’t feel bad about reaching outside of your function. Instead, carefully consider if there is a way to structure your code so that impure functions can be isolated. Prevent them from having broad effects throughout your codebase, and try to use pure functions whenever appropriate.

Let’s take a look at the code below, which grabs an element from the DOM and changes its background color to red:


function changeElementToRed() {
    var foo = document.getElementById('foo');
    foo.style.backgroundColor = "red";
}

changeElementToRed();

There are two problems with this piece of code, both solvable by transitioning to a pure function:

  1. This function is not reusable at all; it’s directly tied to a specific DOM element. If we wanted to reuse it to change a different element, we couldn’t.
  2. This function is hard to test because it’s not pure. To test it, we would have to create an element with a specific ID rather than any generic element.

Given the two points above, I would rewrite this function to:


function changeElementToRed(elem) {
    elem.style.backgroundColor = "red";
}

function changeFooToRed() {
    var foo = document.getElementById('foo');
    changeElementToRed(foo);
}

changeFooToRed();

We’ve now changed changeElementToRed() to not be tied to a specific DOM element and to be more generic. At the same time, we’ve made it pure, bringing us all the benefits discussed previously.

It’s important to note, though, that I’ve still got some impure code—changeFooToRed() is impure. You can never avoid this, but it’s about spotting opportunities where turning a function pure would increase its readability, reusability, and testability. By keeping the places where you’re impure to a minimum and creating as many pure, reusable functions as you can, you’ll save yourself a huge amount of pain in the future and write better code.

Conclusion#section8

“Pure functions,” “side effects,” and “referential transparency” are terms usually associated with purely functional languages, but that doesn’t mean we can’t take the principles and apply them to our JavaScript, too. By being mindful of these principles and applying them wisely when your code could benefit from them you’ll gain more reliable, self-documenting codebases that are easier to work with and that break less often. I encourage you to keep this in mind next time you’re writing new code, or even revisiting some existing code. It will take some time to get used to these ideas, but soon you’ll find yourself applying them without even thinking about it. Your fellow developers and your future self will thank you.

About the Author

Jack Franklin

Jack Franklin is a developer evangelist at Pusher in London. He works heavily with ES2015, React, and a variety of front end technologies and is usually found hunched over Vim typing furiously.

20 Reader Comments

  1. 
    function setElementBackground(id, color) {
        document.getElementById(id).style.backgroundColor = color;
    }
    
    setElementBackground('foo', 'red');
  2. I’ve never heard this referred to as “pure functions” before. In other languages it’s known as dependency injection. Often dependency injection is at the class level, but it applies just as much at the function/method level.

    Good read 🙂

  3. I’ve never heard of this as “dependency injection”. In fact, it’s a pretty big misunderstanding of what each term denotes (pure/impure and dependency injection).

    The authors definition in the first section is absolutely correct.

  4. Pure functions don’t depend on anything that’s not passed in, or in other words, injected. Same concept. You can’t achieve purity if you don’t pass in all the dependencies.

    I’m not arguing that the author is wrong in any way, I was just noting that it’s interesting how similar concepts are referred to differently in different language contexts.

  5. Hey all,

    I wanted to leave a comment to address something that Kent C Dodds and others mentioned on Twitter – the function below:

    
    function changeElementToRed(elem) {
        elem.style.backgroundColor = "red";
    }
    

    Isn’t actually strictly pure because it takes the element and mutates it, in this case by changing its background colour. This means it’s not pure because it has a _side effect_ – that is, it mutates rather than return a new copied object.

    In JavaScript it’s very hard to write pure functions that do manipulate the DOM (without using a React-esque library) and therefore I’ll often strive for the above, which whilst not actually pure, are “mostly pure”, if you like, and still offer many benefits that are discussed throughout this article.

    I hope that helps, and apologies for not making that clear in the original article.

  6. People are going to argue over what the definition of “pure” means, as seen when people start talking about side effects of mutability. We’re really talking about inversion of control using dependency injection (i.e. you pass everything the function needs into the function versus the function reaching outside itself for anything).

    It’s a great technique everyone should know.

  7. @jack: A function, that does not return a value is called a method. A method cannot be pure, because its result is always `undefined` and is usable only for it’s side-effects, therefore not pure. What’s missing in your definition is mention of side-effects, like mutating the DOM, or performing I/O (like AJAX, or `console.log`).

    (well, technically function() {} is a pure function, but you can just replace its result with `undefined` because of referential transparency).

    There’s a trick to make any function pure: instead of performing side-effects, make it return a function (functions are first-class in JS), that performs side-effects, e.g.

    function changeColorFactory(id, color) {
      return function() {
        document.getElementById(id).style.backgroundColor = color;
      }
    }
    

    Remember that you can return also a list or object for multiple return values, which is even elegant to consume using ES6 destructuring. This way, it’s possible to structure a program to perform side-effects (returned functions from pure functions) from a single place and test 100% all the rest of it. Such strict separation is probably rarely practical, just mentioning it as an extreme.

    You can also do automatic testing of pure functions, see Haskell’s quickcheck.

    Dependency injection is just passing a function as a parameter to another function. This is usable to isolate side-effects performing functionality and easily mock it for testing. So:

    function changeColor($document, id, color) {
      $document.getElementById(id).style.backgroundColor = color;
    }
    
    mock = createMockDocument('getElementById', ...); // returns a document object, that asserts a method is called, it returns a certain object, which contains a setter property, which is called with certain parameters (implementation is rather lenghty and far from trivial, hence skipping)
    
    changeColor(mock, 'foo', '#00C0D3'); // mock's machinery kicks in and asserts all is as expected
    

    All examples in this article and my comment are simple enough to look contrived, but’s *invaluable* to be able to make robots test for you more complicated codebase, and most codebases tend to grow and complicate.

  8. When a function changeElementToRed(elem) updates the DOM it’s really a bit silly to call that a “side effect” – the whole purpose of the function is to update the DOM, so when it does so that is the desired effect. (You can argue that it isn’t “pure” because it mutates something, and that’s fine – I’m just saying that the term “side effect” is ridiculous in that context.)

  9. please, delete my upper comment, something is wrong in the interaction with my browser and ALA 🙁

    As I said, all code examples here are contrived, that’s why “side-effect” looks silly to you. But there are whole languages, which are very strict in the way they allow you to perform side-effects thus catching a huge class of JS-would-be-bugs on the compiler level.

    Now imagine a function, that does an AJAX call, then does a complex, lengthy transformation on the received data and then sets innerHTML of some element, to bring a more real-world example.

    In this case it makes sense to extract a pure function, that accepts an object and returns an HTML string. You can easily test this function:

    const inputs = [{input1}, {input2}, ...];
    const expectations = ['expected result 1', '..', ...];
    
    const testsPass = inputs.every((input, index) => myPureTransformator(input) === expectations[index]);
    console.log(testsPass ? 'yep' : 'nope');
    

    and perform your side-effects outside the function:

    fetch(URL).then(response => document.getElementById()[removed] = sanitize(myPureTransformator(response)));

  10. Back in the day it was very easy to just reuse some old scripts on new sites. But we soon found out that not only does this not future proof the site but it becomes a nightmare to debug.

    Making Javascript ‘pure’ like you say definitely helps and so this article resonated with me as the points made are valid and make a lot of sense. Thanks for sharing.

  11. Dependency Injection is probably not well-known in JS or HTML development, but it’s quite popular in lower-level language like Java or .NET.

    Also I think method is nothing about not returning a value. Method usually means a function member of a class (in OOP-sense, not CSS), or object.

  12. mr_tawan and knagurski: Knowledge of functional purity and dependency injection have less to do with the level of programming language developed in, and more to do with the programming paradigm the developer subscribes to.

    If you subscribe to the test-driven development paradigm, you will likely know about dependency injection. If you have used functional programming languages like F#, Haskell, or some Lisps, you will know about functional purity.

    Both dependency injection and functional purity are important concepts to understand, but they are not identical. For a function to be strictly pure, it has to only use variables supplied by it’s input, it can’t modify the variables it has been supplied, and it can’t have side affects, meaning that it can only return a value and cannot perform actions that affect the state of the system it runs on.

    As you can see, there is some overlap between this and dependency injection, but the concepts are definitely distinct.

  13. Javascript is most important programming language which is allow to attract user to visit your website. you should use the javascript code for action of website to make more attractive.

  14. “In JavaScript it’s very hard to write pure functions that do manipulate the DOM (without using a React-esque library)”

    I strongly disagree. It’s not hard (well, technically, it’s impossible, because JS isn’t pure period, but for the perfectly workable purposes of “pure” in a JS context), and certainly no harder than in other languages (heck, pure FP languages like Haskell took YEARS to settle on a principled way to do it, not because of the pure part, but because of the manipulation part!) and it certainly doesn’t require behemoths like React (React is literally not pure from the get go, when you React.render!). JS, on the other hand, had the ability to create pure functions that described effects on day 1!

    To write a pure JS function that manipulates the DOM, you just need an extra layer of execution delay above the layer that describes the manipulation. People do this all the time without realizing it whenever they write functions that use closures or create thunks. Take a function that accepts a selectorString and then returns a function that _will_ retrieve that string the _next_ time it’s called. That’s a pure function, because when you call it, you always get back a function describing the same operation.

    On a more structured level, it’s also possible and not that hard to create a type interface like IO which allows you to compose entire sequences of functions in a pure way, even if they have effects.

  15. Here’s a gist laying out the basic IO approach I’m talking about, along with some helpers for Arrays/Promises that make it pretty easy to describe dom-traversal and mutation all without running the effect (at least until it comes time to explicitly run it).

  16. Real benefit is in Object manipulation. Without pure functions, at some point, you can easily override some property without even knowing that.

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

Nothing Fails Like Success

Our own @zeldman paints the complicated catch-22 that our free, democratized web has with our money-making capitalist roots. As creators, how do we untangle this web? #LetsFixThis