Lesson notes

A determenistic function always returns the same value for a particular input (arguments).

const surfaceAreaCalculator = (radius) => {
  return 4 * 3.14 * square(radius);
}

surfaceAreaCalculator is a deterministic function.

A nondeterministic function will not always return the same value for a particular input.

A function that returns current weather for a location is nondeterministic: weather is always changing, so we can't be sure what answer the function will give.

Another example is a random number generator:

Math.random();      // 0.6822304980945362
Math.random();      // 0.34656303876811245
Math.random();      // 0.44983037125501646

Side effects: how a function changes the outside world.

surfaceAreaCalculator doesn't have any side effects. It doesn't change anything outside its boundaries.

console.log function has a side effect: it prints something onto the screen.

Another example:

let a = 0;

const f = () => {
  a = a + 1;
  return true;
}

f();

Function f changes the value of a global variable a. This variable is in the outside world from the function's point of view, and function f changes it. So, f has side effects.

Both f and console.log have side-effects, but they are deterministic. f always returns true, and console.log always returns undefined.

The things functions return have nothing to do with the way they affect the outside world.

console.log("Hello!");      // prints "Hello!", but returns undefined

**Fewer side effects a function has — the better. **

When a function has no side effects and is deterministic — we call it a "pure" function. Pure functions are:

  • easier to read
  • easier to debug
  • easier to test
  • the order they are called in doesn't matter
  • easy to make them work in parallel (simultaneously)

Pure functions don't care about time. Non-determinism and side effects add the notion of time. If a function depends on something that might or might not happen and changes something outside its walls, then it suddenly becomes dependent on time.

Optional reading

Optional watching


Lesson transcript

You have created plenty of functions already, and most of them work like this: take some arguments, calculate something using the arguments, return the answer. Your functions, even those that use other functions, are nice in this way: they are predictable, stable. For example, the surface area function you made in the beginning: give it a number and it will return a number. Give the same number again — you'll get the same result. In fact, no matter how many times you're going to repeat the call, you'll get the same result for the same input.

While calculating the return value, this function doesn't take into account anything else, only the argument. It doesn't care what time it is, what weather it is, what other functions are working in the program, what values are there in some variables outside. Functions like this are called deterministic.

This word comes from physics and philosophy. A detirministic system is something that produces the same output from a given starting condition. Some philosophical theories explore the idea that reality follows a predetermined path, and everything happens the way it happens because it couldn't happen otherwise. There is no free will, and you were destined to watch this video today.

Anyway, let's back up. In the context of programming, a deterministic function is a function which will always produce the same output for a particular input.

const surfaceAreaCalculator = (radius) => {
  return 4 * 3.14 * square(radius);
}

You might be wondering how else a function can behave. Well, a non-deterministic function is not easily predictable, and its output depends on something else.

For example, think of a function that takes a zip code and returns the current weather. It's probably connecting to a weather server via internet and gives different answers at different times, because, well, the weather changes.

An even simpler example would be a random number generator. Usually, any programming language has some built-in way to generate random numbers. In JavaScript it looks like this:

Math.random();      // 0.6822304980945362
Math.random();      // 0.34656303876811245
Math.random();      // 0.44983037125501646

As you see, every time you call it with the same arguments (in this case — no arguments at all) — the output is new. This function is non-deterministic, but that's the whole point. A random number generator should give you different numbers by definition, even though the calls look the same.

Deterministic functions are better in many ways, but not all functions can be deterministic, and that's okay. A good rule of thumb is — if a function can be deterministic, then it should be.

Deterministic functions are predictable, less fragile and they are easier to think about. Using them is easier too, constructing complex structures and programs.

Since deterministic functions always produce the same output for the same input, they can be optimized: they can remember the output for a certain input, and the next time this input comes in — just return the remembered value instead of doing the whole computation over again. This is guaranteed to be okay if the function is deterministic.

We have to touch one more idea before talking about the most beautiful and nice type of functions. There is a notion of side effects: how a function changes the outside world.

That example above — surfaceAreaCalculator function — doesn't have any side effects. It doesn't change anything outside its boundaries.

Your good friend console.log function has a side effect: it prints something onto the screen. This screen thing is definitely outside the function, it's the computer, the world where the function lives.

Or, consider the following code:

let a = 0;

const f = () => {
  a = a + 1;
  return true;
}

f();

Function f changes the value of a global variable a. This variable is in the outside world from the function's point of view, and function f changes it. So, f has side effects.

Again, this is not bad, after all, a program without side effects will not be useful. We want our programs to do something, to somehow change the world — show something on the screen, make a noise, send an email, etc. But it's possible to minimize side effects in your functions and programs, and it's a good idea, really.

While both f and console.log have side-effects, they are deterministic. f always returns true, and console.log always returns undefined. The things functions return have nothing to do with the way they affect the outside world.

console.log("Hello!");      // prints "Hello!", but returns undefined

Do not confuse printing and returning. Printing onto the screen is just an action, this is what console.log does. But it also returns a value — it always returns undefined.

If f returned the value of a instead of true, then it would be a non-deterministic function with side effects:

let a = 0;

const f = () => {
  a = a + 1;
  return a;
}

f();

You can never be sure about what value f will return until you know something else. It depends on some outside factor, namely, the current value of a.

Fewer side effects a function has — the better.

When a function has no side effects and is deterministic — we call it a "pure" function. It's so predictable, clean and transparent. In this way, pure functions are close to the functions in math. x square will always give the same result for the same value of x, and computing square of x doesn't change x itself.

Everything about pure functions screams "easier": they are easier to read, easier to debug, easier to test. Pure functions in a system do not depend on anything else by definition, so the order they are called in doesn't matter. This means it's easy to make them work in parallel, for example, simultaneously on different processors or even different computers.

Pure functions live in a timeless realm. The whole notion of time, of actions happening in some order, doesn't apply to pure functions. They don't care about time. When you look at pure functions, read the code, debug, test or use just them — you don't have to think about time, about what happened before and will happen after. This is liberating, and all the "easier" things about pure functions are consequences of this fact.

Non-determinism and side effects add the notion of time. If your function depends on something that might or might not happen and changes something outside its walls, then it suddenly becomes dependent on time. It now matters when in time does this function call happen. It becomes harder to think and test and work, because you have an additional dimension to consider.