No history yet

Functional Programming Fundamentals

Immutability is Predictability

In programming, we spend a lot of time changing things. We update user profiles, add items to a shopping cart, and modify application state. How we manage these changes is critical. The functional approach champions immutability, which means that once data is created, it cannot be changed.

This might sound strange. How do you get anything done if you can't change things? The key is that instead of modifying existing data, you create new data with the updated values. Think of it like a signed contract. If you need to change the terms, you don't scribble on the original; you draft a whole new contract that reflects the changes.

Let's see what happens when we ignore this principle. Here, we directly mutate an object:

const user = {
  name: 'Alex',
  emailVerified: false
};

function verifyEmail(account) {
  account.emailVerified = true; // Direct mutation!
  return account;
}

verifyEmail(user);

// The original 'user' object has been changed.
console.log(user); // { name: 'Alex', emailVerified: true }

This direct change is called a mutation, or a side effect. Now, the original user object is permanently altered. In a small script, this is manageable. In a large application, multiple functions could be mutating the same object, leading to bugs that are incredibly difficult to trace. You end up asking, "Where did this change come from?"

The immutable approach avoids this chaos. Instead of changing the original object, we create a copy with the updated information:

const user = {
  name: 'Alex',
  emailVerified: false
};

function verifyEmailImmutable(account) {
  // Create a new object by copying the old one
  // and overriding the property we want to change.
  return { ...account, emailVerified: true };
}

const updatedUser = verifyEmailImmutable(user);

// The original 'user' object is untouched.
console.log(user); // { name: 'Alex', emailVerified: false }

// We have a new object with the change.
console.log(updatedUser); // { name: 'Alex', emailVerified: true }

Here, the user object remains unchanged. The function returns a completely new object. This makes our code predictable. We know exactly what our functions do, and we don't have to worry about them secretly changing data elsewhere in our application. This is a core reason why frameworks like React rely heavily on immutability for managing state updates efficiently.

Pure Functions and Clear Promises

The idea of immutability leads directly to pure functions. A pure function is a function that, given the same input, will always return the same output and has no observable side effects.

Let's break that down into two simple rules:

  1. Same input, same output. A function add(2, 3) should always return 5. It shouldn't return 6 on Tuesdays or 4 when the database is slow. Its output depends only on its inputs.
  2. No side effects. The function doesn't change anything outside of its own scope. It doesn't modify its input arguments, log to the console, write to a file, or make a network request. It just calculates its result and returns it.

A pure function is like a vending machine: put in the same money and selection, and you get the same snack every time. It doesn't do anything else.

This function is impure because its output depends on an external variable, taxRate, which can change. It's not self-contained.

let taxRate = 0.07;

// Impure function
function calculateTotal(price) {
  return price + (price * taxRate); // Depends on external state
}

To make it pure, we must pass everything it needs as an argument. Now its output depends only on its inputs.

// Pure function
function calculateTotalPure(price, rate) {
  return price + (price * rate);
}

calculateTotalPure(100, 0.07); // Always returns 107

This property of a function's output depending only on its inputs is called referential transparency. It means you can replace a function call with its resulting value without changing the program's behaviour. For example, you can replace calculateTotalPure(100, 0.07) with 107 anywhere in your code, and it will work exactly the same. This makes code easier to reason about, test, and even optimize.

Functions as Building Blocks

In JavaScript, functions are "first-class citizens." This means they can be treated like any other value, such as a number or a string. You can store them in variables, pass them as arguments to other functions, and return them from functions. This capability is the foundation for higher-order functions.

Higher-Order Function

noun

A function that either takes one or more functions as arguments, or returns a function as its result.

You've likely already used higher-order functions without thinking about it. Array methods like map, filter, and reduce are classic examples. They take a function as an argument to tell them how to operate on the array's elements.

const numbers = [1, 2, 3, 4];

// .map is a higher-order function.
// It takes a function as an argument.
const doubled = numbers.map(n => n * 2); // [2, 4, 6, 8]

// .filter is also a higher-order function.
const evens = numbers.filter(n => n % 2 === 0); // [2, 4]

Higher-order functions allow us to abstract away the how from the what. In the code above, map handles the looping for us; we only need to provide the logic for what to do with each element. This leads to more declarative and readable code. Instead of writing a for loop, we simply declare that we want to double each number.

Another powerful application is creating functions that return other functions. This is useful for creating specialised, configurable functions.

// This higher-order function returns a new function.
function createMultiplier(factor) {
  // This returned function "remembers" the 'factor'.
  return function(number) {
    return number * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(10)); // 20
console.log(triple(10)); // 30

By embracing immutability, pure functions, and higher-order functions, you can write code that is more predictable, testable, and easier to understand. These concepts form the bedrock of the functional programming style.

Quiz Questions 1/5

What is the core principle of immutability in programming?

Quiz Questions 2/5

Which of the following is a key characteristic of a pure function?