Functional Programming in JavaScript: A Beginner's Guide

Learn the basics of functional programming in JavaScript. Master the basics of functional programming in JavaScript with this comprehensive beginner's guide. Learn how to write more efficient, reusable, and maintainable code with functions, lambdas, higher-order functions, and other functional programming concepts.

An introduction to functional programming in JavaScript #1: Introduction

Introduction

If you are a JavaScript developer, there’s a good chance you’ve come across concepts like “functional programming”, “functors”, “closures”, or “immutability”. You might be wondering what the heck all these things mean. Maybe you’ve already done some more reading. Or maybe you’re trying to incorporate some of the FP practices into your code.

If that’s the case, then I guess we’re in the same boat!

I started using JavaScript professionally some 2 years ago. Since then, I’ve been trying to deepen my knowledge of functional programming.

In the beginning, I was baffled by things like “currying”, or my colleague telling me to “use _map_ instead of _forEach_ and avoid these side effects”.

I’ve learned a lot since then. Trying to understand FP practices has been a really stimulating experience — and still is!

Now, I want to distill what I’ve learned so far into a series of articles — a series I wish I’d read at the beginning of my journey. I am hoping this will prove useful for people with a background like the one I had a couple years ago.

What is Functional Programming?

Functional programming is a programming paradigm — a way of thinking about, and structuring your code. It’s an alternative to procedural or object-oriented programming.

FP aficionados would say they aim to optimize for code reusability, readability, and testability. The tools in their toolboxes are functions, ways of composing them to model complex behavior, and avoiding shared state and side effects.

If these words mean nothing to you — don’t worry, we’ll get there!

Why should you learn Functional Programming?

There’s a ton of articles out there on the Internet that go on about what makes Functional Programming better than Objective Programming. About how it produces more readable code that is easier to extend, test, and maintain; is more declarative, and so on.

And I agree with most of those. I don’t want to repeat the same arguments over again, so I would like you to take a look at a few of the links above.

What I will tell you, though, is something else. I will tell you why you should consider learning Functional Programming. I’ll share what was in it for me, and what might be in it for you as well.

It is becoming mainstream

If you look at the ever-changing JS ecosystem, the increasing adoption of functional programming practices is clear. A lot of the cool new things are heavily influenced by FP:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

There’s a good chance you already are or will be using one of these tools. If you understand the concepts behind Functional Programming well, you will master these frameworks and libraries faster. If you don’t, well, you’re going to be running behind.

It is challenging

Have you ever thought that the last time your brain was working at full speed was, like, in college?

I certainly have. I mean, my job is challenging. I spend a lot of time thinking hard: about the business problem, architecture of the system, team dynamics, and lots of other stuff.

It’s just not the feeling I got in, say, calculus classes. There, I needed to twist my brain to understand all the concepts, and how they play with each other.

Well, trying to learn functional programming is kind of like that. Not without reason — functional programming is deeply rooted in maths.

There will be a lot of new terms, and trying to digest all this knowledge might make your head spin. But you’re in for a pleasant surprise — as soon as these things click, you will feel huge satisfaction.

I believe that if you enjoyed maths in college, you will enjoy learning this stuff as well.

Now that I think about it… it’s probably not the best way to get you hooked on functional programming, is it?

It’s something new

I believe that learning a new programming paradigm is beneficial even if you don’t plan to switch to it in your day-to-day work. Not only because it’s challenging — but also because it feels different.

Trying a new style of coding will expose you to alternative ways of doing things. It will force you to think about problems from a different point of view.

You might think that your time is better spent mastering the tools you’ve been using so far. I know it feels more rewarding, pragmatic, and productive. It deepens your tech expertise.

I believe that we, as software developers, also need to broaden our experience. It helps us communicate with other developers better, especially if they have different backgrounds. It helps us make better decisions — by approaching problems from new directions.

Learning a new programming language is a great way to broaden your expertise. Learning a new programming paradigm is even better.

OK, but… why JavaScript?

If you’ve decided you want to learn FP, the next question is: is JavaScript a good way to do it?

Let me start by saying that JavaScript is probably not the best way to learn Functional Programming concepts.

Other, more functional languages, have immutable data structured built in. JavaScript doesn’t, you need to use a library for that. Using one, however, makes your code more verbose at the points where the library’s code meets yours.

JavaScript tries to look like Java, with its class, new , and all this stuff.

The JavaScript standard library kind of sucks. It certainly was not designed with FP concepts in mind.

The point here is: there are a lot of arguments to be made about JS not being the best choice for Functional Programming.

On the other hand, JavaScript has a lot of things going for it, that make functional programming easier:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

Also, and I think this is the most important argument: JavaScript is very popular, and is getting more popular by the day.

If you are going to use JavaScript anyway, why not try to learn the new paradigm where it can actually be useful?

Let’s get started!

In the next parts of this series, I will introduce some FP concepts — starting from the very basic ones. I’m planning to cover the following aspects:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

The assumption we will be working with is that we all know JavaScript basics, ES6, and so on. It is not a hard requirement in order to understand FP concepts. It will, however, make it easier to work through examples and trying things on your own.

An introduction to functional programming in JavaScript #2: Functions. Duh!

Introduction

In this part, we will focus on the vocabulary and basic concepts connected with functional programming.

Sadly, there won’t be a lot of code involved. On the bright side, once we understand the terminology, we will be able to discuss more complex subjects comfortably.

Functions

As you can imagine, the most important thing in functional programming is, well, a function.

We all know a function when we see one. It’s basically an organised (and named, for the most part) piece of code that does stuff.

function add (x, y) {
  return x + y
}

When it comes to FP, though, we are interested in a specific point of view on what a function is: a mathematical one.

Math… not even once?

Even though we won’t have to deal with algebra ourselves, we need to acknowledge that functional programming is deeply rooted in maths.

And in simple, mathematical terms, a function is a machine that produces an output given an input.

The one interesting thing is that there can only be one output for a given input. Which means, if we provide the function with the same input, we expect it to always do the same exact thing, and return the same value.

This sounds trivial, but it’s actually a strong requirement. This mathematical definition has significant consequences:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

Functions that meet those criteria are called pure functions in programming, and they are crucial to the functional paradigm.

Pure functions

Let’s look at some examples of functions in JavaScript to build up an intuition about what a pure function is.

function coin () {
  return Math.random() < 0.5 ? 'heads' : 'tails'
}

The coin function is not pure, because it doesn’t always produce the same result given the same (empty) input - it’s not deterministic.

let firstName = 'krzysztof'

function uppercaseName (lastName) {
  return `${firstName.toUpperCase()} ${lastName.toUpperCase()}`
}

The uppercaseName function is not pure, because it depends on a variable that’s out of its control. We can’t be sure it will always produce the same result given the same arguments.

let user = {
  firstName: 'Krzysztof',
  age: '26'
}

function happyBirthday () {
  user.age = user.age + 1
}

The happyBirthday function is not pure, because in addition to accessing out-of-control variables, it does not return anything.

function calculatePrice (unitPrice, noOfUnits, couponValue = 0) {
  return unitPrice * noOfUnits - couponValue;
}

The calculatePrice function is pure. It doesn’t use any variables out of its control, is deterministic, and we can confidently say that it will always return the same result for the same combination of input arguments.

So what?

Why does it all matter? There are a couple of reasons why pure functions are better than impure ones:

  1. They are easier to read
  2. In order to understand what a function does, you only need to read its body.
  3. They are easier to reason about There’s no need to look for external dependencies, the context in which the function is called, etc. None of this matters for pure functions.
  4. They are easier to test If you want to test a function that is pure, you only need to call it with some arguments and see if the result is what you wanted it to be. No complicated setup required.
  5. They can be more performant If we know that for a given input, the function will always produce the same output, we can cache (memoize) the result so we don’t have to recalculate it when the function is called again.

Using pure functions makes your code more maintainable — because it makes it easier to manage side effects. In the next parts we will learn what side effects are and why, sadly, computer programs can’t be pure functions “all the way down”.

Now that we know what pure functions are, let’s focus on the next function-related term: first-class functions.

First-class functions

A “first-class function” is, unlike the “pure function”, not a practical concept that’s useful on a daily basis. It does, however, come up when thinking about characteristics of programming languages.

You can say a programming language has “first-class functions”, if functions can be used just like any other values, i.e.:

  1. They are easier to read
  2. In order to understand what a function does, you only need to read its body.
  3. They are easier to reason about There’s no need to look for external dependencies, the context in which the function is called, etc. None of this matters for pure functions.
  4. They are easier to test If you want to test a function that is pure, you only need to call it with some arguments and see if the result is what you wanted it to be. No complicated setup required.
  5. They can be more performant If we know that for a given input, the function will always produce the same output, we can cache (memoize) the result so we don’t have to recalculate it when the function is called again.

Functional programming without first-class functions would be impossible (or at least very awkward). Here’s an example illustrating why functions are first-class objects in JavaScript:

function add (a, b) {
  return a + b
}

function multiply (a, b) {
  return a * b
}

const operations = { // here we're using add and multiply as regular values
  add,
  multiply
}

operations.add(1, 2)

As we said, in JavaScript functions can be passed around — between different functions. But… why would you want to do that?

Well, passing functions in and out of functions is a common practice in functional programming — and a very powerful one. Which brings us to…

Higher-order functions

Functions that “operate” on other functions are called higher-order functions. By operate, we mean they can either (or both):

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

The examples of this are really common in the JavaScript world. One of the examples is the Array.prototype.map function in the standard library. It takes a function and applies it to every element in the array:

const numbers = [1, 1, 2, 3, 5, 8]
const transformFunction = x => x + 2

numbers.map(transformFunction)

When it comes to returning functions from other functions, here’s an (a bit contrived, I have to admit) example:

function makeGreeter (greeting) {
  return function greet (name) {
    return `${greeting}, ${name}!`
  }
}

// or, using the ES6 syntax:

const makeGreeter = greeting => name => `${greeting}, ${name}!`

const greet = makeGreeter('Hello')
console.log(greet('Krzysztof'))

As you can see, these functions (map and makeGreeter) don’t accept/return regular values as we know them. They operate on functions.

You may already be familiar with some of the higher-order functions, like:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

Functional programming is all about composing small, reusable, and generic functions into more complex ones. Given that, you can expect us to be discussing a lot of different higher-order functions in the posts to follow.

An introduction to functional programming in JavaScript #3: State

Introduction

Last time we discussed some more terminology related to functional programming. You now understand concepts like higher-order functions, first-class functions, as well as pure functions — and this is something we will build on today.

We will see how pure functions can help us avoid bugs related to managing state. You will also get to know (and hopefully — understand) some new vocabulary: side effects, immutability, and referential transparency.

First, let’s see what we mean by application state, what it’s needed for, and what issues can arise if we’re not dealing with it carefully.

What is state?

The term state can be used in multiple contexts. The notion that we’re interested in is application state.

Simply put, you can consider application state to be an entirety of:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

It is basically all of the information that represents what is currently happening in the application.

In the following examples, both counter and user variables contain information about the application state in a given moment in time:

let counter = 0
let user = {
  firstName: 'Krzysztof',
  lastName: 'Czernek'
}

counter = counter + 1

user.firstName = 'KRZYSZTOF'
user.lastName = 'CZERNEK'

The code snippet above is an example of a _global state _— every piece of code can have access to both counter and user variables.

We can also talk about a local state, like in the snippet below:

const countBiggerThanFive = numbers => {
  let counter = 0
  for (let index = 0; index < numbers.length; index++) {
    if (numbers[index] > 5) {
      counter++
    }
  }
  return counter
}

countBiggerThanFive([1, 2, 3, 4, 5, 6, 7, 8, 9, -5])

Here, the counter variable holds the current state of the countBiggerThanFive function invocation.

Every time we call the countBiggerThanFive function, a new variable will be created and initialized with 0. Then, it gets updated while iterating over numbers, and finally ceases to exist after it is returned from the function. It is only ever accessed by the code inside the function – this is why we can consider it a part of the local state.

Similarly, the index variable represents the current state of the for loop – no code outside the loop can read or change it.

The point is, application state is not only about global variables — it can be defined at various “levels” of the application code.

Why does it matter? Let’s dig a bit deeper.

Shared state

State, as we can see, is necessary for our programs. We need to keep track of what’s happening and be able to update the application state to model behavior.

We might want to use a more global state to hold information that may be useful for any piece of code in our program.

Let’s say we use a currentUser variable to keep information about a user who’s currently logged in. We can imagine different parts of our application need this data to make “decisions” – about authorization, customization, etc.

It may be tempting to have currentUser be a global variable so that every function in the codebase can access and change it as needed. This is what we mean when we talk about a shared state.

But this comes with the territory — if every function in your application is able to make changes to currentUser, you need to consider what happens if they do. And if they do change it, it affects multiple other functions that also have access to currentUser.

This can lead to nasty bugs and make reasoning about application logic more difficult. It’s not easy to track down where and when the change occurred if it could happen literally anywhere.

The general rule of thumb is — the more global a piece of state is, the more careful you need to be when changing it. For a more local state, the consequences will not be that far-fetched.

Mutable shared state

Having a global state that is read-only is not quite as troublesome as having mutable shared state.

Let’s see what consequences can a mutable shared state have on our application’s readability and maintainability.

It makes reasoning about code harder

In general, the more “open” a piece of state is to changes from different places of your codebase, the more difficult it is to follow what’s its current value at a point in time.

Let’s say you have a couple of functions that can (and do) make changes to the same global variable. You end up in a situation where there may be multiple possible sequences of these functions being called one after another.

If you want to prove that a variable like this is always in correct (logical) state, you might need to consider all possible flows of interactions — and there may be infinitely many :)

It hurts testability

To write a unit test for a function, you need to predict circumstances it can run under. You then write test cases for these — to make sure your function always behaves correctly.

It is easier to do it when the only things your function depends on are its parameters.

If your function, on the other hand, uses and changes shared state — you will have to pre-configure this state for all tests. You may also need to reset the shared state afterwards so that other functions that depend on it can be tested correctly.

It affects performance

If your function depends on the mutable shared state, there is no easy way to run it concurrently — even if it conceptually makes sense.

Different “instances” of the function, running concurrently, would access and mutate the same piece of state, potentially influencing each other’s behavior in unpredictable ways.

Handling issues like that is not trivial. Even if you can find a way to do it reliably, you will most likely introduce more complexity and make your functions less modular and reusable.

Okay, so what do we do if we want to avoid having a global variable to represent and keep track of application state? Let’s look at some possibilities.

Use parameters instead of state

The simplest way to avoid issues caused by a shared state is to verify your functions do not reference it if they don’t have to. Let’s see an example:

const currentUser = getCurrentUser()

const getUserBalance = () => {
  return currentUser.balance
}

console.log(getUserBalance())

We can see how the getUserBalance function references currentUser – which is, in fact, shared state.

On the surface, it all looks good — but in reality, we have introduced an implicit coupling between getUserBalance and currentUser. If we wanted to, for example, change the name of currentUser, we would need to change it inside getUserBalance as well.

To mitigate this, we can change getUserBalance to have currentUser passed in to it. Even though the change looks trivial, it makes for a more readable and maintainable code.

const currentUser = getCurrentUser()

const getUserBalance = user => {
  return user.balance
}

console.log(getUserBalance(currentUser))

Immutability

Even if you do pass all necessary variables to a function explicitly, you still need to be careful.

Generally speaking, you need to make sure you don’t mutate any of the arguments passed in to your function. Let’s see an example:

const getUserBalance = user => {
  return user.balance
}

const rewardUser = user => {
  user.balance = user.balance * 2
  return user
}

const currentUser = getCurrentUser()
console.log(getUserBalance(currentUser))

const rewardedUser = rewardUser(currentUser)
console.log(getUserBalance(currentUser), getUserBalance(rewardedUser))

The issue here is that the rewardUser function not only returns a user with doubled balance – it also changes the user variable that was passed in. It effects in having both currentUser and rewardedUser variables reference the same, updated value.

This kind of operation makes the logic more difficult to follow.

Here’s how this can be improved:

const getUserBalance = user => {
  return user.balance
}

const rewardUser = user => {
  return {
    ...user,
    balance: user.balance * 2
  }
}

const currentUser = getCurrentUser()
console.log(getUserBalance(currentUser))

const rewardedUser = rewardUser(currentUser)
console.log(getUserBalance(currentUser), getUserBalance(rewardedUser))

In general, you need to make sure that your functions almost* always return new objects and don’t mutate their arguments. This is what we refer to as immutability.

One way to go about it is to simply keep this rule in mind and use it dogmatically across your codebase. In my experience, it can work pretty well.

Other options include using external tooling to provide immutable collections, like Immutable.js from Facebook. Not only does it guard you against mutating data, but also tries to reuse data structures efficiently to improve performance.

For a more comprehensive overview, please read Cory House’s article on approaches to immutable values. Don’t worry about “React” in the title — the techniques outlined there apply to JavaScript in general.

** the only reason to mutate arguments (I’m aware of) is to optimize performance. Before you go down this path, make sure to profile your application.*

Back to functions

Okay, but what does it have to do with functional programming — you may ask.

Last time, we discussed functions that we called pure but didn’t really get specific. Now, with our newly acquired knowledge, we can adjust our definition.

We said pure functions meet the following criteria:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

We can now see that these can be rephrased.

“T_hey can’t depend on anything except their input”_ and “they need to be deterministic” really means that pure functions can’t access or mutate shared state.

“They have to return a single value” means that there should be no observable effects of calling the function, other than the return value.

When a function does mutate shared state or have other observable consequences, we say it produces side effects. What that means is that the outcome of calling it is not contained to this function’s internal state only.

Let’s dig a bit deeper into side effects.

Side effects

There are a couple of different types of side effects, including:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

Here are some examples of functions that produce side effects:

const users = {}

// Produces side effects – mutates arguments and global state
const loginUser = user => {
  user.loggedIn = true
  users[user.id] = user
  return user
}

// Produces side effects – writes data to storage
const saveUserToken = token => {
  window.localStorage.setItem('userToken', token)
}

// Produces side effects – writes to console
const userDisplayName = user => {
  const name = `${user.firstName} ${user.lastName}`
  console.log(name)
  return name
}

// Produces side effects – uses userDisplayName that produces side effects
const greetingMessage = user => {
  return `Hello, ${userDisplayName(user)}`
}

// Produces side effects – makes an API call
const getUserProfile = user => {
  return axios.get('/user', {
    params: {
      id: user.id
    }
  })
}

If you think about it, for your program to be useful it needs to produce side effects. Otherwise, you wouldn’t even be able to observe the effects of its operation.

Computer programs can’t be “pure functions all the way down”.

We don’t want to create useless, theoretical programs.

Functional programming is not about writing code entirely without side effects. It’s about structuring your code in a way that side effects are easy to manage and contained to a small portion of the application. It’s about making your program easier to understand and maintain.

There is one more term that is often used in this context — referential transparency. Although it’s a little bit more complicated and has fancy words in its name, we are now fully equipped to understand how it ties into pure functions.

Referential transparency

We say that a function is referentially transparent when we can replace the expression that calls our function with the value this call produces — without changing the program’s behavior.

Even though it intuitively makes sense, we need to understand that for a function to be referentially transparent, it needs to be pure (not produce side effects).

Let’s see an example of a function that is not referentially transparent:

const getUserName = user => {
  console.log('getting user profile!')
  return `${user.firstName} ${user.lastName}`
}

const getUserData = user => {
  return {
    name: getUserName(user),
    address: user.address
  }
}

getUserData({
  firstName: 'Peter',
  lastName: 'Pan',
  address: 'Neverland'
})

It seems like, for the getUserData to still work correctly, a call to getUserName could be replaced with its result, like so:

const getUserData = user => {
  return {
    name: `${user.firstName} ${user.lastName}`,
    address: user.address
  }
}

getUserData({
  firstName: 'Peter',
  lastName: 'Pan',
  address: 'Neverland'
})

However, we did change the program’s functionality here — it used to log stuff to the console (a side effect!), and now it doesn’t. It looks like a trivial change, but it does indicate that getUserName was not referentially transparent in the first place (and neither was getUserData, to be fair).

Summary

We now understand what it means to manage application state, what functional programmers mean by immutability, referential transparency, and side effects — and what issues can a shared state introduce.

An introduction to functional programming in JavaScript #4: Closures

Introduction

We have already covered a lot of ground when it comes to functional programming terminology.

It is time to turn our attention to another concept that is useful when trying to program in a more functional fashion. We will be talking about closures.

In the part 2, we have introduced a notion of first-class functions, as well as higher-order functions. We have seen how they can be used to compose our programs’ complex logic out of multiple small, single-purpose functions. We will now see what’s the relationship between these concepts and closures — and why higher-order functions wouldn’t be nearly as useful without closures.

In the part 3, we have focused on application state and pure functions. Today, we are going to discuss how we can model and store application state using closures.

But enough with the mystery…

What is a Closure?

A closure is a mechanism present in some programming language that allows functions to “remember” the variables that were present in their outer scope when they were defined.

Okay, that’s a lot to process. Let’s take it from the top.

What do we mean by “their outer scope”? There are a couple of different “scopes” that variables/constants can be defined in. Let’s take a look at a few of them.

The first, and the most straightforward of them is local scope:

const userGreetingMessage = user => {
  const localGreeting = 'Hello'
  const message = `${localGreeting}, ${user.firstName}`
  return message
}

userGreetingMessage({firstName: 'Krzysztof'})

Take a closer look a line 3. Hopefully, we can all intuitively understand how the localGreeting variable is available there – it has been defined just a line earlier, in the same function. It belongs to the userGreetingMessage’s local scope.

Then, there’s global scope:

const globalGreeting = 'Hello'

const userGreetingMessage = user => {
  const message = `${globalGreeting}, ${user.firstName}`
  return message
}

userGreetingMessage({firstName: 'Krzysztof'})

Here, we have access to the globalGreeting variable in line 4 because it has been defined in the global scope of the codebase – so it is accessible everywhere.

So far, so good

The previous examples represent common patterns that hopefully are easy to understand. Now, let’s turn our attention to closures.

First, we will take a look at a (contrived example of) higher order function.

const makeGreeter = () => {
  const closureGreeting = 'Hello'
  
  return user => {
    const message = `${closureGreeting}, ${user.firstName}`
    return message
  }
}

userGreetingMessage = makeGreeter()

userGreetingMessage({firstName: 'Krzysztof'})

Here, we can see that the closureGreeting can be referenced in line 5, even though it is not defined in the local scope of the inner (lines 4-7) function, or in the global scope of the application.

Let’s try to follow some of the execution steps here:

  1. They are easier to read
  2. In order to understand what a function does, you only need to read its body.
  3. They are easier to reason about There’s no need to look for external dependencies, the context in which the function is called, etc. None of this matters for pure functions.
  4. They are easier to test If you want to test a function that is pure, you only need to call it with some arguments and see if the result is what you wanted it to be. No complicated setup required.
  5. They can be more performant If we know that for a given input, the function will always produce the same output, we can cache (memoize) the result so we don’t have to recalculate it when the function is called again.

In line 12, the outer function (makeGreeter) has already exited, so we could expect the closureGreeting variable to be gone. But this is not what happens – we can see that the inner function still has access to the **closureGreeting** variable.

This is possible because of closures.

A closure is a mechanism that allows inner functions to remember the variables that were present in their outer scope when they were defined.

We can see closures in action by using console.dir:

const makeGreeter = () => {
  const closureGreeting = 'Hello'
  
  return user => {
    const message = `${closureGreeting}, ${user.firstName}`
    return message
  }
}

userGreetingMessage = makeGreeter()

console.dir(userGreetingMessage)

In the screenshot, 3rd line from the bottom, we can see that the userGreetingMessage function remembers the value of closureGreeting inside its [[Scopes]] attribute. This is what is referenced to when the function is called.

A different take on closures

There are different ways of looking at closures that might be helpful when trying to understand how they work.

One way to recognize closures is to keep in mind that every time there is a function defined inside another function — the inner one has access to variables defined in the outer function.

This applies to variables explicitly defined in the outer function (as we’ve seen earlier), but also to arguments of the outer function. Also, all of this applies to arrow functions as well as the standard function notation.

An example showcasing this:

const makeGreeter = greetingMessage => user => {
  return `${greetingMessage}, ${user.firstName}`
}

userGreetingMessage = makeGreeter('Hello')
userGreetingMessage({firstName: 'Krzysztof'}

We can also look at closures as first-class functions with bound variables.

Gotcha!

There is one thing we need to be aware of when trying to follow the flow of a piece of code that uses closures. Consider the following example:

const makeGreeter = () => {
  let closureGreeting = 'Hello'
  let innerGreeter = user => {
    return `${closureGreeting}, ${user.firstName}`
  }
  
  closureGreeting = 'Hi'
  
  return innerGreeter
}

userGreetingMessage = makeGreeter()

userGreetingMessage({firstName: 'Krzysztof'})

Running this code will yield Hi, Krzysztof instead of Hello, Krzysztof we might be expecting.

This is because inner functions “remember” values of variables at a point of time when the outer function returned, and not when the inner function was defined.

In practice, we shouldn’t be reassigning variables as we did here, but that’s just something worth keeping in mind.

But… why?

If closures look like a pretty complex subject, that’s because they are. The good thing is, they quickly become intuitive once we start using them.

Nevertheless, one has to wonder: why do we need closures in the first place? Let’s explore some use cases.

Function composition, higher-order functions

Using closures can help us write more readable code in a functional way — using generic functions to create more specialized ones.

Consider this example:

const getObjectAttributeByName = attributeName => obj => obj[attributeName]

const getFirstName = getObjectAttributeByName('firstName')

const krzysztof = { firstName: 'Krzysztof', lastName: 'Czernek' }

getFirstName(krzysztof) // "Krzysztof"

getObjectAttributeByName, as well as some of the functions discussed earlier, represents a common way of defining multi-parameter functions – using currying. We will learn more about this technique in the future, but for now, you can hopefully understand what is going on.

We call getObjectAttributeByName in line 3, providing the attributeName argument. What is returned is an inner function that accepts obj as a parameter, and returns obj[attributeName].

By the time we call the inner function, getFirstName, in line 7, the getObjectAttribute function has already returned. However, thanks to closures, the information about attributeName being equal to 'firstName' is still there and can be used.

There are more benefits to using currying and, a related technique, partial application. We will focus on that in the next parts, but we need to remember that it is all made possible (or rather, useful) because of closures. If not for them, the inner functions could not access outer functions’ arguments and variables.

Encapsulation

There is one additional benefit of using closures — they can be used to achieve encapsulation.

Consider this example, inspired by Douglas Crockford:

const counter = initialValue => {
  let currentValue = initialValue
  return () => {
    console.log(`The current value is: ${currentValue++}`)
  }
}

const countFromFive = counter(5)

countFromFive() // The current value is: 5
countFromFive() // The current value is: 6
countFromFive() // The current value is: 7

This looks trivial. There is one important consequence of using closures for storing currentValue. Once we execute the counter function, there is no way for us to access currentValue directly and, say, modify it later.

This example is, of course, contrived (in addition to having side effects) — but we can see how this pattern can apply to more complex data structures as well.

We could use a closure to only expose a few “interface” functions to interact with and keep the internal data representation inaccessible. This would not have been possible if we used, say, plain objects to model such behavior.

Summary

There are two final points to keep in mind regarding closures:

  1. They are easier to read
  2. In order to understand what a function does, you only need to read its body.
  3. They are easier to reason about There’s no need to look for external dependencies, the context in which the function is called, etc. None of this matters for pure functions.
  4. They are easier to test If you want to test a function that is pure, you only need to call it with some arguments and see if the result is what you wanted it to be. No complicated setup required.
  5. They can be more performant If we know that for a given input, the function will always produce the same output, we can cache (memoize) the result so we don’t have to recalculate it when the function is called again.

We now understand what the mysterious term closure means, and how it can be useful for encapsulation and function composition.

If you want to learn more about closures, I recommend these resources:

  • React — with its reusable components and pure functions
  • Redux — and the way it forces you to avoid mutating state
  • Reason — and its functional approach and immutability
  • Elm — being a functional language that compiles to JavaScript
  • Underscore.js, lodash, and Ramda — all having FP utility functions baked in

I hope this comes in handy.

#javascript #function #web-development

Functional Programming in JavaScript: A Beginner's Guide
1 Likes55.20 GEEK