JavaScript · Memory · Closure · Execution Context
Understanding Closures in JavaScript
Ever wondered how a function in JavaScript can remember variables even after it’s done executing? In this article, you’ll explore how execution contexts, the call stack, and closures work together to give your code persistent memory.

Oscar Mutwiri
Software engineer
Before we learn about closure, we need to remind ourselves how a function is executed in JavaScript.
Understanding Function Execution in JavaScript
const a = 20;
function add2Numbers (num1, num2) {
const result = num1 + num2;
return result;
}
const output = add2Numbers(5, a);
JavaScript follows the thread of execution, goes through the code line by line, and saves the data to use later in its memory. In our function, a = 20 is saved first, followed by the add2Numbers function, and then output is saved, which remains uninitialized until the result of the function call returns. All this is done in the global execution context (an execution context is the environment in which code is currently being executed), which is the first or the default created by the JS engine. Then when the function add2Numbers is called, a new execution context is created. An execution context has 2 parts: the memory and the thread of execution. Here the parameters num1 and num2 are stored and initialized to the arguments 5 and 20, respectively. Next, const result is also stored, assigned to the result of the execution (num1 + num2). Then JS retrieves the value of the result and returns it. Finally, the execution context is deleted.
The Role of the Call Stack
Amidst all this we have the call stack, which keeps track of the function currently running and the data accessible to it. Anytime an execution context is created, it gets pushed inside the call stack and popped from the top once the function finishes executing. The control is then returned to the context below it, which in our case is the global context since it is always the first to be pushed in the call stack. One disadvantage of this is that the memory vanishes together with the execution context, and anything that was stored in it cannot be referenced. This is where closure comes to the rescue.
How Closure Work
function createCounter() {
let count = 0;
return function increment() {
count = count + 1;
console.log(count);
};
}
const myCounter = createCounter();
myCounter();
myCounter();
Here, we store createCounter in global memory and myCounter awaiting the results of calling createCounter. A new execution context is then created, count is defined and initialized to 0, then the definition or the code of the increment function is returned and the context removed. The code of the increment is then assigned to myCounter. Now, when myCounter() is called, the count increases by 1, which extends the execution of functions we just discussed above. When increment is returned, it does not return as a function alone; it carries with itself the link to its surroundings, which is the memory containing any variables that were defined there, which in our case is count. Or as explained by Will Sentance of Frontend Masters, the increment definition carries a backpack to the global memory with a link to everything that was in the memory of createCounter's execution context. With this link, it means that we have access to the count variable even though the execution context is removed. This is what is called closure. Because we now have persistent memory, count increments every time myCounter() is called.
Why Closure Matter
Closure gives functions persistent memory, making it applicable in areas such as iterators, which are able to remember their current position in the sequence.