JavaScript closures: How functions remember external variables

April 21st, 20235 mins read

JavaScript closures: How functions remember external variables

Have you ever wondered how a function in JavaScript can remember variables that are defined outside of it? This is where closures come in!

Closures are a powerful feature in JavaScript that allow functions to remember variables from their parent scope, even after the parent function has returned.

In this article, you'll explore how closures work in JavaScript and how they can be used in real-world scenarios.

Understanding Scope in JavaScript

Before we dive into closures, let's first talk about scope in JavaScript.

Scope refers to the visibility and accessibility of variables in different parts of a program. In JavaScript, there are two types of scope: local and global.

Local variables are defined inside a function and can only be accessed within that function. For example:

function greet() {
	const name = 'John';
	console.log(`Hello, ${name}!`);
}

greet(); // Output: "Hello, John!"
console.log(name); // ReferenceError: name is not defined

In this example, name is a local variable that can only be accessed within the greet() function.

Global variables, on the other hand, are defined outside of any function and can be accessed anywhere in the program. For example:

const age = 30;

function celebrateBirthday() {
	age++;
	console.log(`Happy ${age}th Birthday!`);
}

celebrateBirthday(); // Output: "Happy 31st Birthday!"
console.log(age); // Output: 31

In this example, age is a global variable that can be accessed inside and outside the celebrateBirthday() function.

What are Closures?

Now that we understand scope in JavaScript, let's talk about closures.

A closure is a function with access to variables from its parent scope, even after the parent function has returned. Here's an example:

function counter() {
	let count = 0;
	return function () {
		count++;
		console.log(count);
	};
}

const incrementCounter = counter();
incrementCounter(); // Output: 1
incrementCounter(); // Output: 2
incrementCounter(); // Output: 3

In this example, the counter() function returns another function that increments and logs a count variable. Even though the counter() function has returned, the inner function still has access to the count variable because of closure.

Closures also work in arrow functions:

const counter = () => {
	let count = 0;
	return () => {
		count++;
		console.log(count);
	};
};

const incrementCounter = counter();
incrementCounter(); // Output: 1
incrementCounter(); // Output: 2
incrementCounter(); // Output: 3

How Closures Work

To understand how closures work, let's break down the previous example:

function counter() {
	let count = 0;
	return function () {
		count++;
		console.log(count);
	};
}

const incrementCounter = counter();
  • We define a counter() function that initializes a count variable to 0 and returns another function.
  • We assign the returned function to a variable incrementCounter.
  • When we call incrementCounter(), it increments the count variable and logs its value.

The key here is that the returned function still has access to the count variable, even though it was defined in the parent counter() function. This is because the returned function forms a closure over the count variable, allowing it to remember and use it even after the parent function has returned.

Common Use Cases for Closures

Closures are commonly used for creating private variables and functions. This is because variables and functions defined inside a closure are not accessible from outside the closure. Let's take a look at an example:

function createPerson(name) {
	let age = 30;

	function greet() {
		console.log(`Hello, my name is ${name} and I'm ${age} years old.`);
	}

	function celebrateBirthday() {
		age++;
	}

	return {
		greet,
		celebrateBirthday,
	};
}

const john = createPerson('John');
john.greet(); // Output: "Hello, my name is John and I'm 30 years old."
john.celebrateBirthday();
john.greet(); // Output: "Hello, my name is John and I'm 31 years old."
console.log(john.age); // Output: undefined

In this example, we have a createPerson() function that returns an object with two methods: greet() and celebrateBirthday().

The greet() method logs a message that includes the name and age variables. The celebrateBirthday() method increments the age variable. The age variable is defined in the parent createPerson() function and is not accessible from outside the closure.

If we try to access the age variable directly from outside the closure, we get undefined. This is because the age variable is not defined in the global scope, and is only accessible from within the closure created by createPerson().

Without closures, we would have to make the age variable a global variable in order to access it from both greet() and celebrateBirthday(). This can lead to naming conflicts and other issues, making closures a cleaner and more modular solution.

Conclusion

Closures are a powerful feature in JavaScript that allow functions to remember variables from their parent scope, even after the parent function has returned.

This allows for more flexible and modular code, and can help prevent bugs and unexpected behavior.

Understanding closures is an important step in becoming a proficient JavaScript developer.

Try what you have learned in the interactive code editor below.

function createPerson(name) {
	let age = 30;

    function greet() {
    	document.write("Hello, my name is " + name + "and I'm" + age +  "years old.");
    }

    function celebrateBirthday() {
    	age++;
    }

    return {
    	greet,
    	celebrateBirthday,
    };

}

const john = createPerson('John');
john.greet(); // Output: "Hello, my name is John and I'm 30 years old."
john.celebrateBirthday();
john.greet(); // Output: "Hello, my name is John and I'm 31 years old."
document.write(john.age); // Output: undefined

Have fun coding!


Intersted in seeing some videos? Check out my recent uploads.

What Are MCPs? I Used Cursor to Create a Repo and Push Code to GitHub
25:55

October 8th, 2025

What Are MCPs? I Used Cursor to Create a Repo and Push Code to GitHub

Understanding Docker Multi-Stage Builds (with Example) - Fast and Efficient Dockerfiles
21:26

September 1st, 2025

Understanding Docker Multi-Stage Builds (with Example) - Fast and Efficient Dockerfiles

3 Tips to Optimize Docker Images | Reduce Size, Improve Performance & Security
19:04

August 22nd, 2025

3 Tips to Optimize Docker Images | Reduce Size, Improve Performance & Security

Passwordless Auth in Node.js with Magic Links (Step-by-Step)
26:47

July 7th, 2025

Passwordless Auth in Node.js with Magic Links (Step-by-Step)

One Security Check Most Devs Forget in Their Signup Flow
10:11

May 20th, 2025

One Security Check Most Devs Forget in Their Signup Flow

How to Implement OAuth in Your Node.js Backend (GitHub & Google Login)
30:03

May 15th, 2025

How to Implement OAuth in Your Node.js Backend (GitHub & Google Login)

Next.js JWT Auth Explained: Cookies, Refresh Tokens & 2FA (No Auth.js)
01:02:51

May 6th, 2025

Next.js JWT Auth Explained: Cookies, Refresh Tokens & 2FA (No Auth.js)

Add 2FA to Your Node.js App with otplib
44:19

April 6th, 2025

Add 2FA to Your Node.js App with otplib

How to Run PostgreSQL with Docker Locally
25:48

March 31st, 2025

How to Run PostgreSQL with Docker Locally

Node.js Auth API with JWT, PostgreSQL & Prisma | Vibe Coding
01:23:24

March 30th, 2025

Node.js Auth API with JWT, PostgreSQL & Prisma | Vibe Coding

Create Stunning Emails in React — React-Email & Resend
33:53

March 6th, 2025

Create Stunning Emails in React — React-Email & Resend

Complete Guide — React Internationalization (i18n) with i18next
56:15

February 17th, 2025

Complete Guide — React Internationalization (i18n) with i18next

Build an Animated FAQ Accordion Component in React and Tailwind CSS
30:25

February 8th, 2025

Build an Animated FAQ Accordion Component in React and Tailwind CSS

Learn Shell Scripting With Me Using ChatGPT
47:12

January 30th, 2025

Learn Shell Scripting With Me Using ChatGPT

How to Build a Mega Menu in Next.js – Step-by-Step Tutorial
36:33

January 20th, 2025

How to Build a Mega Menu in Next.js – Step-by-Step Tutorial

The Easiest Way to Make Your Footer Stick to the Bottom || CSS Grid or Flexbox
11:00

January 13th, 2025

The Easiest Way to Make Your Footer Stick to the Bottom || CSS Grid or Flexbox

Form Validation in Next.js with React Hook Form & Zod
27:47

October 31st, 2024

Form Validation in Next.js with React Hook Form & Zod

Styling Active Links in Next.js
08:31

January 20th, 2024

Styling Active Links in Next.js

What is Bun — A JavaScript All-in-one Toolkit
29:01

January 17th, 2024

What is Bun — A JavaScript All-in-one Toolkit

Copy and Paste with JavaScript — 7 lines of code 🔥
01:00

December 27th, 2023

Copy and Paste with JavaScript — 7 lines of code 🔥

Migrate a Node.js Project to use Bun — 60 seconds ⚡️
01:00

December 25th, 2023

Migrate a Node.js Project to use Bun — 60 seconds ⚡️

Build a Slackbot With Slack API and Node.js
38:16

December 19th, 2023

Build a Slackbot With Slack API and Node.js

Technical Writing & Technical Blogging [w/ Asaolu Elijah]
17:58

December 30th, 2020

Technical Writing & Technical Blogging [w/ Asaolu Elijah]