Understand How Javascript Closures Work with Simple Examples


When I first started learning JavaScript, it gave me plenty of headaches. It’s easy to learn but hard to master—a language that can bring you joy and frustration on the same day.
As Douglas Crockford, an author of JavaScript: The Good Parts, once said:
JavaScript is the world’s most misunderstood programming language.
To truly master JavaScript and stand out, you need to understand its core principles and one of the most powerful among them is closures.
I guarantee that learning how closures work will ease your life and save nerves when solving daily problems. If you truly grasp the point, you can implement it in your project as a good practice and I’m sure other team members will appreciate your hard work.
I will explain to you how closures work by providing simple examples and giving you two real cases when they are useful. Big thanks belong to Andrei Neagoie, who gave me huge inspiration for writing this article.
The requirements are familiarity with functions and Javascript runtime environment.
If you want to grasp the main point of why closures are useful, the prerequisite is good knowledge of functions and lexical scoping (or lexical environment). A combination of these two aspects is the core of closures.
So let’s take it over one by one.
Functions
I am pretty sure that you have at least a basic knowledge of functions if you code in Javascript. Otherwise, it would be like baking a cake without cooking tools.
However, you should also know the term which is closely bound with closures and that is higher-order functions.
Higher-order function (HOC)
It’s a type of function that either takes a function as an argument, returns a function as its return value, or both.
Here is a basic example from daily praxis:
If you work with arrays, I suppose you came across array methods such as Array.map()
, Array.filter()
or Array.reduce()
. All of them take a function as an argument. All of them are higher-order functions.
Let’s write another example:
const handleFamily = () => {
const myGrandpa = 'grandpa';
return sayHello = () => {
const myFather = 'father';
return 'Hello there!';
}
}
The handleFamily
function is using a fat arrow function expression, which was introduced in ECMAScript 2015 (ES6). Inside this function, we have declared a variable myGrandpa
and returned another function sayHello
, so it is a Higher Order function.
If we call (invoke) handleFamily()
, this is what we get:
==> Function sayHello
That is pretty logical — we get another function. So in order to get the desired output, we have to call it like this:
handleFamily()()
==> 'Hello there!'
These two brackets one after another basically mean that whatever we return from handleFamily
, we call again. We could also assign the called handleFamily
to a variable so it would be like this:
const holdFamily = handleFamily();
holdFamily();
We tend to use HOC all the time. If you’re a JavaScript programmer or if you’ve ever done any JavaScript, you’ve probably used them all over the place.
Execution scope
It’s also required to know what really happens if a function is called. In Javascript, every function creates its local execution scope (or also the so-called local execution context). What does it mean?
It means, that every variable, which is declared in this scope is also local to this scope.
The outer scope of the function — in our case, global scope — has no access to these variables.
On the other hand, the local scope can access variables from its outer scope. The reason is because of closures, which I explain later in this story.
It is a good practice to write variables, where they are needed. Therefore you might avoid side-effects and memory overloading of the Javascript engine.
In our example, the local execution scope of handleFamily
is where the first opening curly bracket is and ends with the last closing bracket. Its local variables are myGrandpa
and sayHello
.
The sayHello
function has of course also its local execution scope, where myFather
is declared. This means if you want to access this variable from handleFamily
(the outer scope) you get an error because Javascript won’t see it.
Lexical scope
It is a quite fancy name, but yet easy to understand. The best way how to explain to you this term is by dividing these words:
lexical — means where is a code written,
scope — what variables we have access to.
What do I mean by that?
In the first phase, even before executing the code, the Javascript engine will save all variables to the temporary memory of the Javascript engine (also called memory heap) for future usage. At the same time, the engine will recognize which function has access to which variables. This is done by determining function execution scopes and chaining them properly (often so-called scope chaining).
Eventually, in terms of scoping it matters, where a function is written, not where is called.
The coherence
Now, since we are familiar with functions and lexical scoping, let’s combine them. I will modify our handleFamily
function in the following manner:
const handleFamily = () => {
const myGrandpa = 'grandpa';
return sayHello = () => {
const myFather = 'father';
return `Hello ${myGrandpa} and ${myFather}!`
}
}
const hold = handleFamily();
hold();
What do you expect the result would be?
==> 'Hello grandpa and father!'
If you work with JavaScript I assume you are familiar with such behavior, but I think it’s quite important to understand why this happens. So let’s dismantle the whole process.
Dismantling the execution
When I had assigned handleFamily
to a variable hold
, its execution context was executed and popped off the call stack. When this happens, it usually means that the variable environment is destroyed as well. However, every function creates a closure, which has reserved space in the memory heap. If some variable is being referenced in one of the inner functions, the variable is saved in the closure memory waiting for use.
By calling hold()
we execute sayHello
function. The myFather
variable is declared in the same scope and immediately used. On the other hand, myGrandpa
is declared in the outer scope. That means the Javascript engine will go up to the outer scope and search for the myGrandpa
in the closure memory. In our case, the variable is present there.
Theoretically, if myGrandpa
wouldn’t be declared in the outer scope, it would go up to another level (in our case in the global scope) and search for the variable there. If it's still absent, it would return an error.
This is quite a unique behavior compared to other languages. Javascript engine will always make sure that functions have access to all of the variables outside the function using closures.
Finally, after calling sayHello
, it’s popped off the call stack, and variables that are not referenced anywhere are cleaned up from memory by a cleaning mechanism of the Javascript engine called garbage collection.
Why so much enthusiasm
Now you might be thinking that closures are just big hype. But I will show you two cases when they could improve your code in terms of performance and security. I am talking about memory efficiency and data encapsulation.
Memory efficiency
Consider the following function called produceCandy
:
const produceCandy = (index) => {
const candyFactory = new Array(7000).fill('sweet candy');
console.log('the candy factory was established');
return candyFactory[index];
}
The variable
candyFactory
creates an array, which has 7000 items.It logs the text in the console whenever the function is called and returns an item with the desired index.
Now, what is the main problem with this function?
Consider we need to return any items from candyFactory
. In practice, it could be some data we need to get from a database and do something with it:
candyFactory(34);
candyFactory(4574);
candyFactory(875);
// returns ==> the candy factory was established the candy factory was established the candy factory was established sweet candy
It means, candyFactory
is created in the memory heap before execution and destroyed after execution every time it is called.
In our case, it’s not a big issue, but imagine you would call it ten thousand times. It would create the variable and destroy it ten thousand times, which is memory inefficient and might cause performance issues.
So what could be a solution? You’re guessing right — using closures.
I would modify our function like this:
const produceCandyEfficiently = () => {
const candyFactory = new Array(7000).fill('sweet candy')
// candyFactory is stored in the closure memory,
// because it has reference in execution scope of inner function below
console.log('the candy factory was established');
return (index) => {
return candyFactory[index];
}
}
const getProduceCandyEfficiently = produceCandyEfficiently();
getProduceCandyEfficiently(1243);
getProduceCandyEfficiently(6832);
getProduceCandyEfficiently(345);
And after calling getProduceCandyEfficiently
3 times this is the output:
==> the candy factory was established sweet candy
So candyFactory
is created only once no matter how many times you call getProduceCandyEfficiently
. After the last call, this function is eventually removed from the memory.
In tasks such as processing an immense amount of data, it can significantly improve performance. Cool, right?
Data encapsulation
This is another case when closures might be useful.
Suppose we have another candy factory, which monitors the duration time of its production since the function monitorProductivityTime
has been called.
We will store this amount of time in the variable timeWithoutReset
. The value is incremented every second in setInterval
. Along with that, we’d like to also have a function, which would reset this duration time if needed. Let’s call it reset
.
This is what would it look like (try to test it in the browser console to receive proper results):
const monitorProductivityTime = () => {
let timeWithoutReset = 0;
const productivityTime = () => timeWithoutReset++;
const totalProductivityTime = () => timeWithoutReset;
const reset = () => {
timeWithoutReset = -1;
return 'production time has been restarted!';
}
setInterval(productivityTime, 1000);
return {
totalProductivityTime,
reset
}
}
const holdTime = monitorProductivityTime();
holdTime.totalProductivityTime();
When we call holdTime.totalProductivityTime()
In the last line, we are measuring the duration of time from this moment. If you’d like to reset this time, just call holdTime.reset()
.
Because reset
function is inside monitorProductivityTime
, it uses closure for accessing the variable timeWithoutReset
.
However, the main problem with this function is that everybody can access reset
. It could be your new team member, who is not acquainted with the project yet and could mess some things up. Or any third party from outside of your company. This can cause many troubles and security issues in practice, therefore some data or functions should not be directly exposed.
For this reason, you should always keep in mind the principle of least privilege, which is one of the security principles you should observe. It means that data can be accessed only by competent individuals.
The solution to this example might be to return reset
, only if it’s appropriate, e.g. user has a competent role to access this function.
Summary
There’s no question, closures are one of the core principles in Javascript. It brings developers huge benefits if they’re used in the right way, but many times they’re used in code unknowingly.
Therefore, you should catch the following points about closures:
Every function has reserved space in the memory heap for closures.
Javascript engine will always make sure that functions have access to all of the variables outside the function.
When you’re processing an immense amount of data you can optimize memory usage.
When you’re working with sensitive data you can observe the principle of least privilege using encapsulation.
Closures can significantly improve the performance and security of your code.
Thank you for reading.
Subscribe to my newsletter
Read articles from Miroslav Pillár directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Miroslav Pillár
Miroslav Pillár
🚀 Self-taught software developer with a passion for growth and innovation. From an economics background, I transitioned into tech—gaining experience in frontend and fullstack development across financial and healthcare industries. My journey led me to founding Bitloom, a software development company focused on building modern web and mobile solutions.