What Is Functional Programming?

As a programmer, you probably want to write elegant, maintainable, scalable, predictable code. The principles of functional programming, or FP, can significantly aid in these goals.

Functional programming is a paradigm, or style, that values immutability, first-class functions, referential transparency, and pure functions. If none of those words makes sense to you, don’t worry! We’re going to break down all this terminology in this article.

Functional programming evolved from lambda calculus, a mathematical system built around function abstraction and generalization. As a result, a lot of functional programming languages look very mathematical. Good news, though: you don’t need to use a functional programming language to bring functional programming principles to your code. In this post, we’ll use JavaScript, which has a lot of features that make it amenable to functional programming while not being tied to that paradigm.

The Core Principles of Functional Programming

Now that we’ve discussed what functional programming is, let’s talk about the core principles behind FP.

Pure functions

I like to think of functions as machines — they take an input, or arguments, and then output something, the return value. Pure functions don’t have ‘side effects’ or actions that don’t relate to the output of the function. Some potential side effects would be printing a value or logging it out with console.log, or manipulating variables outside the function.

Here’s an example of an impure function:

let number = 2;

function squareNumber() {
  number = number * number; // impure action: manipulating variable outside function
  console.log(number); // impure action: console log-ing values
  return number;
}

squareNumber();

The function below is pure. It takes an input and produces an output.

// pure function
function squareNumber(number) {
  return number * number;
}

squareNumber(2);

Pure functions operate independently from state outside of the function, so they shouldn’t rely on global state, or variables outside of itself. In the first example, we use the number variable created outside the function, and set it inside. This violates the principle. If you’re relying heavily on constantly changing global variables, your code will be unpredictable and hard to trace. It will be more difficult to figure out where bugs are happening and why values are changing. Instead, using only inputs, outputs, and variables local to functions allows for easier debugging.

In addition, functions should follow referential transparency, which means that given a certain input, their output will always be the same. In the above function, if I pass 2 to the function, it will return 4 always. The same is not true for API calls or generating random numbers, as two examples. Given the same input, the output may or may not be returned.

// Not referentially transparent
Math.random();
// 0.1406399143589343
Math.random();
// 0.26768924082159495ß

Immutability

Functional programming also prioritizes immutability, or not directly modifying data. Immutability leads to predictability — you know the values of your data, and they aren’t changing. It makes code simple, testable, and able to run on distributed and multi-threaded systems.

Immutability comes into play frequently when we work with data structures. Many array methods in JavaScript directly modify the array. For example, .pop() directly removes an item from the end of the array and .splice() allows you to take a section of an array. Instead, within the functional paradigm, we would copy the array and in that process remove the element we are looking to eliminate.

// We are mutating myArr directly
const myArr = [1, 2, 3];
myArr.pop();
// [1, 2]
// We are copying the array without the last element and storing it to a variable
let myArr = [1, 2, 3];
let myNewArr = myArr.slice(0, 2);
// [1, 2]
console.log(myArr);

First-class functions

In functional programming, our functions are first-class, which means we can use them like any other value. We can create arrays of functions, pass them as arguments to other functions, and store them in variables.

let myFunctionArr = [() => 1 + 2, () => console.log("hi"), x => 3 * x];
myFunctionArr[2](2); // 6

const myFunction = anotherFunction => anotherFunction(20);
const secondFunction = x => x * 10;
myFunction(secondFunction); // 200

Higher-order functions

Higher order functions are functions that do one of two things: they either take a function as one or more of its parameters, or they return a function. There are many of the first type of higher-order functions built into JavaScript — like map, reduce, and filter which we can use to interact with arrays.

filter is used to return a new array from an old one that contains only values that fit a condition, which we provide.

const myArr = [1, 2, 3, 4, 5];

const evens = myArr.filter(x => x % 2 === 0); // [2, 4]

map is used to iterate through the items in an array, modifying each item according to the provided logic. In the below example, we double each item in an array by passing a function to map that multiplies our value by two.

const myArr = [1, 2, 3, 4, 5];

const doubled = myArr.map(i => i * 2); // [2, 4, 6, 8, 10]

reduce allows us to output a single value based on an inputted array — it’s often used to sum an array, flatten arrays, or group values in some way.

const myArr = [1, 2, 3, 4, 5];

const sum = myArr.reduce((i, runningSum) => i + runningSum); // 15

You could also implement any of these yourself! For example, you could create a filter function like so:

const filter = (arr, condition) => {
  const filteredArr = [];

  for (let i = 0; i < arr.length; i++) {
    if (condition(arr[i])) {
      filteredArr.push(arr[i]);
    }
  }

  return filteredArr;
};

The second type of higher-order function, functions that return other functions, are also a relatively frequent pattern. For example:

const createGreeting = greeting => person => `${greeting} ${person}`

const sayHi = createGreeting("Hi")
console.log(sayHi("Ali")) // "Hi Ali"

const sayHello = createGreeting("Hello")
console.log(sayHi("Ali")) // "Hello Ali"

Currying is a related technique that you may be interested in reading up on as well!

Function composition

Function composition is when you combine multiple simple functions in order to create more complex ones. So, you could have an averageArray function that combines an average function with a sum function that sums up an array’s values. The individual functions are small and could be reused for other purposes, and in combination they perform a more complete task.

const sum = arr => arr.reduce((i, runningSum) => i + runningSum);
const average = (sum, count) => sum / count;
const averageArr = arr => average(sum(arr), arr.length);

Benefits

Functional programming leads to modular code. You have small functions that you can reuse over and over. Knowing the specific functionality of each function means pinpointing bugs and writing tests should be straightforward, especially since the function outputs should be predictable.

In addition, if you are trying to use multiple cores, you can distribute function calls across those cores, so it can lead to more computational efficiency.

How Can You Use Functional Programming?

You don’t need to move completely over to functional programming to incorporate all these ideas. You can even use many of the ideas well in combination with object-oriented programming, which is often thought of as its opponent.

React, for example, incorporates many functional principals like immutable state, but also used the class syntax primarily for years. It can also be implemented in nearly any programming language — you don’t need to write Clojure or Haskell, unless you really want to.

Functional programming principles can lead to positive results in your code, even if you’re not a purist.