DEV Community

loading...
Cover image for Pure vs Impure Functions

Pure vs Impure Functions

sanspanic profile image Sandra Spanik Updated on ・4 min read

Software engineering is full of jargon. Occasionally, to grasp the true meaning of the seemingly simplest of words, one must waddle through many murky layers of complexity (fancy defining this, anyone?). Thankfully, other times, outwardly inaccessible words can be demystified pretty easily. In this article, we'll deal with the latter case, breaking down pure vs impure functions.

person thinking about the definition of this

1. Pure Functions 👼

To be considered pure, functions must fulfil the following criteria:

  • they must be predictable
  • they must have no side effects
➡️ Pure functions must be predictable.

Identical inputs will always return identical outputs, no matter how many times a pure function is called. In other words: we can run a pure function as many times as we like, and given the inputs remain constant, the function will always predictably produce the same output. Kind of like when you're a pizza-loving person with lactose intolerance. No, this time won't be different, so stop ogling that 16-incher your flatmate ordered.

➡️ Pure functions must have no side-effects.

A side-effect is any operation your function performs that is not related to computing the final output, including but not limited to:

  • Modifying a global variable
  • Modifying an argument
  • Making HTTP requests
  • DOM manipulation
  • Reading/writing files

A pure function must both be predictable and without side-effects. If either of these criteria is not met, we're dealing with an impure function.

An impure function is kind of the opposite of a pure one - it doesn't predictably produce the same result given the same inputs when called multiple times, and may cause side-effects. Let's have a look at some examples.

// PURE FUNCTION 👼
const pureAdd = (num1, num2) => {
  return num1 + num2;
};

//always returns same result given same inputs
pureAdd(5, 5);
//10
pureAdd(5, 5);
//10

//IMPURE FUNCTION 😈
let plsMutateMe = 0;
const impureAdd = (num) => {
  return (plsMutateMe += num);
};

//returns different result given same inputs
impureAdd(5);
//5
impureAdd(5);
//10
console.log(plsMutateMe)
//10 🥳 I'm now double digit, yay!
Enter fullscreen mode Exit fullscreen mode

In the above example, the impure version of the function both changes a variable outside its scope, and results in different output, despite being called with identical input. This breaks both rules of pure functions and as such, it's pretty clear we're dealing with an impure function here.

But let's have a look at an example of an impure function that is not so easy to tell apart from its pure counterpart.

//IMPURE FUNCTION 😈
const impureAddToArray = (arr1, num) => {
  arr1.push(num);
  return arr1;
};

impureAddToArray([1, 2, 3], 4);
//[1,2,3,4]
impureAddToArray([1, 2, 3], 4);
//[1,2,3,4]
Enter fullscreen mode Exit fullscreen mode

Given the same inputs, the function above will always return the same output. But it also has the side effect of modifying memory in-place by pushing a value into the original input array and is therefore still considered impure. Adding a value to an array via a pure function instead can be achieved using the spread operator, which makes a copy of the original array without mutating it.

//IMPURE FUNCTION 😈
const impureAddToArray = (arr1, num) => {
  //altering arr1 in-place by pushing 🏋️
  arr1.push(num);
  return arr1;
};

// PURE FUNCTION 👼
const pureAddToArray = (arr1, num) => {
  return [...arr1, num];
};
Enter fullscreen mode Exit fullscreen mode

Let's look at how we'd add to an object instead.

// IMPURE FUNCTION 😈
const impureAddToObj = (obj, key, val) => {
  obj[key] = val;
  return obj;
};
Enter fullscreen mode Exit fullscreen mode

Because we're modifying the object in-place, the above approach is considered impure. Below is its pure counterpart, utilising the spread operator again.

// PURE FUNCTION 👼
const  pureAddToObj = (obj, key, val) => {
  return { ...obj, [key]: val };
}
Enter fullscreen mode Exit fullscreen mode

Why should I care?

If the differences in the above examples seem negligible, it's because in many contexts, they are. But in a large-scale application, teams might choose pure over impure functions for the following reasons:

  • Pure functions are easy to test, given how predictable they are
  • Pure functions and their consequences are easier to think about in the context of a large app, because they don't alter any state elsewhere in the program. Reasoning about impure functions and potential side-effects is a greater cognitive load.
  • Pure functions can be memoized. This means that their output, given certain inputs, can be cached when the function first runs so that it doesn't have to run again - this can optimise performance.
  • The team lead is a Slytherin obsessed with the purity status of both blood and functions (are we too old for HP references? I think not).

Pure functions are also the foundation of functional programming, which is a code-writing paradigm entire books have been written about. Moreover, some popular libraries require you to use pure functions by default, for example React and Redux.

Pure vs Impure JavaScript Methods

Certain JS functions from the standard library are inherently impure.

  • Math.random()
  • Date.now()
  • arr.splice()
  • arr.push()
  • arr.sort()

Conversely, the below JS methods are typically associated with pure functions.

  • arr.map()
  • arr.filter()
  • arr.reduce()
  • arr.each()
  • arr.every()
  • arr.concat()
  • arr.slice()
  • Math.floor()
  • str.toLowerCase()
  • the spread syntax ... is also commonly used to create copies

1. Comparison

So who comes out as a winner in this battle between good and evil? Actually, nobody. They simply have different use cases, for example, neither AJAX calls, nor standard DOM manipulation can be performed via pure functions. And impure functions aren't intrinsically bad, they just might potentially lead to some confusion in the form of spaghetti code in larger applications.

Sidenote: I resent the widely held sentiment that the word spaghetti should ever be associated with anything negative. Get in my tummy and out of coding lingo, beloved pasta. 🍝

I'll leave you with a quick tl;dr comparison table.

👼 Pure Functions 👼 😈 Impure Functions 😈
no side-effects may have side-effects
returns same result if same args passed in no matter how many times it runs may return different result if same args passed in on multiple runs
always returns something may take effect without returning anything
is easily testable might be harder to test due to side-effects
is super useful in certain contexts is also super useful in certain contexts

Discussion (16)

pic
Editor guide
Collapse
toanleviet95 profile image
Toan Le Viet

Yah I agree that Impure Function is also super useful in certain contexts. Sometimes, we use in-place solution to reduce the space complexity instead of creating copies with Pure Function

Collapse
eecolor profile image
EECOLOR

There are a few use cases where an implementation of a function could be considered 'not pure' while, from the outside, it is pure.

let complexValue = null
export function getComplexValue() {
  return complexValue || (complexValue = calculateComplexValue())
}

function calculateComplexValue() {
  // this is always the same
  ...
  return ...
}
Enter fullscreen mode Exit fullscreen mode
export function mapValues(o, f) {
   return Object.entries(o).reduce(
    (result, [k, v]) => (result[k] = f(v, k, o), result),
    {}
  )
}
Enter fullscreen mode Exit fullscreen mode
Collapse
lukaszahradnik profile image
Lukáš Zahradník

A side-effect is any operation your function performs that is not related to computing the final output

This isn't exactly true. You can have side effect, that is related to computing of your output that will make the function impure.

You also call arr.map(), arr.filter(), arr.reduce(), arr.each(), arr.every() pure. Are they really pure if they have impure predicates/callbacks?

Collapse
sanspanic profile image
Sandra Spanik Author • Edited

Great, I want to be exact in the language I use so thank you for commenting :) I edited “considered pure” to “typically associated with pure functions”.

As for the definition, I found it elsewhere. Before I update it, let me try and see if I’m parsing what you’re saying correctly: you’re saying that while my definition describes some side-effects, it doesn’t describe ALL side-effects, because there are some which are indeed related to computing the final output. Is that right?

Collapse
lukaszahradnik profile image
Lukáš Zahradník

Nice.

Yes, that's what I am saying. For example fetching data and then using them for some computation.

Collapse
brad_beggs profile image
Brad Beggs

.sort() is also impure, sorting the input in-place.

Collapse
lukeshiru profile image
△ LUKE知る

Indeed! One way of going around it is doing:

[...array].sort();
Enter fullscreen mode Exit fullscreen mode
Collapse
brad_beggs profile image
Brad Beggs

And in particular, you would need to assign the result of using the rest operator to a new variable get the sorted array?

For example:
const array = [1,2,6,5,4]
let newArray = [...array].sort((a,b) => a-b )

console.log(newArray) // [1, 2, 4, 5, 6]
console.log(array) // [1,2,6,5,4]

Collapse
sanspanic profile image
Sandra Spanik Author

Thanks for the suggestion, I've updated the list!

Collapse
sebring profile image
J. G. Sebring

I'd like a way to flag pure functions like an annotation or similar. Last time I looked this up I didn't find any so I'm throwing this out - anyone doing this, and how?

example, jsdoc style

/**
* @pure
*/
add (x, y) {
 return x + y
}
Enter fullscreen mode Exit fullscreen mode
Collapse
rbauhn profile image
Robin Bauhn

Nice article!
I think that it is worth pointing out could be that spread syntax only is pure for primary types. For objects and that sort of stuff it is only the reference that's being copied so any mutations would also mutate the orignal object.

Collapse
nikhils4 profile image
Nikhil Singh

Great article! I would like to add that writing a pure function is more about how much control/ confidence you have on that piece of code, rather than following a bunch of rules and assuming it as a pure function. By confidence/ control, I mean how sure are you that if a certain input is any given later in time, that piece of code will give the same output.

That's how I see pure functions.

Collapse
ash_bergs profile image
Ash

Great lunchtime read! Thanks for the well-researched article!

Collapse
hkly profile image
hkly

Great write up, educational and entertaining! Definitley going to be using the Slytherin reference re function purity now with my teammates lol

Collapse
saroj8455 profile image
Collapse
electrocucaracha profile image
Victor Morales

Ansible refers pure functions as Idempotent Playbooks which are useful for retries