The JavaScript engine handles this for you. The engine allocates memory during initialization and frees it up once we don't need it anymore.
However, this automaticity is a potential source of confusion: it can give developers the false impression that they don't need to worry about memory management.
And being a new developer, you don't need to worry about it. In this article, we will walk through the concepts of memory management in JS and related interview questions.
Let's start by asking a very basic question. I have initialized a variable like this. let x = 5
. now tell me what happens next in terms of memory management?
Where is this going to be stored?
JavaScript engines have two places where they can store data: The memory heap and stack.
Heaps and stacks are two data structures that the engine uses for different purposes.
Stack: Static memory allocation
.A stack is a data structure that JavaScript uses to store static data. Static data is data where the engine knows the size at compile time. For example, primitive data types like string
, number
, boolean
etc.
Since the engine knows that the size won't change, it will allocate a fixed amount of memory for each value.
The process of allocating memory right before execution is known as static memory allocation.
Because the engine allocates a fixed amount of memory for these values, there is a limit to how large primitive values can be.
The limits of these values and the entire stack vary depending on the browser.
A stack overflow occurs when the stack exceeds its maximum size. This can happen when there’s an infinite loop or recursive functions that don’t return.
Heap: Dynamic memory allocation
Since the stack has limited memory space, the Javascript engine stores the non-primitive variables(whose memory allocation is not known at the run time) in the heap memory which is equivalent to unlimited memory space.
The heap is a different space for storing data where JavaScript stores objects and functions.
Unlike the stack, the engine doesn't allocate a fixed amount of memory for these objects. Instead, more space will be allocated during run time as needed.
Allocating memory this way is also called dynamic memory allocation.
How Objects are stored?
All variables start by pointing to the stack. In the case of primitive variables, their value is stored in the stack while in the case of non-primitive variables, their reference to the heap memory is stored in the stack. The reference is the address of the memory block of that object in the Heap memory space.
How can one change the value of an array declared with const?
You must have seen this piece of code.
const num = 5;
num = 10; // throw error: redeclaration is not allowed for const
const arr = [1, 2, 3];
// below operations will not throw an error
arr[1] = 10; // mutating a value of array
arr.push(5); // adding a new value
arr.pop(); // deleting a value
The answer to this question lies in the fact that how an array(object) is stored in memory. Once you declare a variable with const
/let
, it will point to its value/reference stored in the stack. In the case of an array, it will point to the heap memory reference stored in the stack. Since any of the above operations on the array are not changing that reference, all these operations are allowed to be done on an array declared with const
.
that is why, we never declare the array/object with let keyword
However, if you will try to reinitialize the object declared with const
, it will throw an error.
const arr = [5, 6, 7];
arr = [1, 2, 3]; // throws error, trying to assign a const
Mutable vs immutable variables in Javascript
Primitive data types are immutable in Javascript
Primitive values are immutable, which means that instead of changing the original value, JavaScript creates a new one.
Primitives are known as being immutable data types because there is no way to change a primitive value once it gets created. => A primitive value can be replaced, but it can’t be directly altered.
let name = 'John'; // allocates memory for a string
const age = 24; // allocates memory for a number
name = 'John Doe'; // allocates memory for a new string
name[0] = 'Z'; // this will do nothing to name variable,
Non-primitive data types are mutable in Javascript
However, it is important to understand that objects (including arrays and functions) assigned to a variable using const are still mutable. Using the const declaration only prevents the reassignment of the variable identifier.
Non-Primitives are known as
mutable
data types because we can change the value after creation.Secondly, Objects are not compared by value, they are being compared byreference
.
"use strict";
const s = [5, 6, 7];
s = [1, 2, 3]; // throws error, trying to assign a const
s[7] = 45; // works just as it would with an array declared with var
Garbage collection
We now know how JavaScript allocates memory for all kinds of objects, but if we remember the memory lifecycle, there's one last step missing: releasing memory.
The majority of memory management issues occur at this phase. The most difficult aspect of this stage is determining when the allocated memory is no longer needed.
Just like memory allocation, the JavaScript engine handles this step for us as well. More specifically, the garbage collector takes care of this.
Once the JavaScript engine recognizes that a given variable or function is not needed anymore, it releases the memory it occupies.
The main issue with this is that whether or not some memory is still needed is an undecidable problem, which means that there can't be an algorithm that's able to collect all the memory that's not needed anymore at the exact moment it becomes obsolete.
Some algorithms offer a good approximation to the problem. For example, The reference-counting garbage collection and the mark and sweep algorithm.
Memory leaks
To make the Garbage collection algorithm more efficient, we as a developer should also take care of scenarios that can cause memory leaks.
Global variables - Never declare anything in global space
Storing data as global variables is probably the most common type of memory leak.
In the browser, for instance, if you use var
instead of const
or let
—or leave out the keyword altogether—the engine will attach the variable to the window
object.
The same will happen to functions that are defined with the function
keyword.
user = getUser();
var secondUser = getUser();
function getUser() {
return 'user';
}
const arrowFn = () => return user;
All three variables, user
, secondUser
, arrowFn
and getUser
, will be attached to the window
object.
Global variables are never garbage collected throughout the lifetime of an app. They occupy memory as long as the app is running.
This only applies to variables and functions that are defined in the global scope. If you want to learn more about this, check out this article explaining the JavaScript scope.
Solution:-
You should always avoid using the global scope whenever you can, including global variables.
Apart from adding variables accidentally to the root, there are many cases in which you might do this on purpose.
You can certainly make use of global variables, but make sure you free space up once you don't need the data anymore.
To release memory, assign the global variable to
null
.window.users = null
Try to declare variables/functions in the local scope as much as you can. In this way, they are garbage collected properly.
Avoid this by running your code in strict mode.
Strict mode ("use strict") will help you prevent memory leaks and trigger console errors in the example above.
When you use ES modules or transpilers like TypeScript or Babel, you don’t need it as it’s automatically enabled.
You can also use
IIFE
if you have to use global functions/variables. JavaScript Immediately Invoked Function Expressions (IIFEs) are functions that are executed when they are initialized.
Forgotten timers or callbacks/event listeners
When you set up an event listener or setInterval function to execute every x seconds, it has the potential to make the memory usage of the application go up.
Unregister the event listener once no longer needed, by creating a reference pointing to it and passing it to
removeEventListener()
.Always use
clearInterval()
when you know the function won’t be needed
Other best practices to prevent memory leak
Destructure objects and use only the fields you need from an object or array rather than passing around entire objects or arrays to functions, closures, timers, and event handlers.
Conclusion
In this context, we learned about JavaScript memory management, JavaScript memory leaks, the problems they can cause, and how to prevent them.
Memory leaks are caused due to flaws in our code, following the instructions listed above to avoid possible leaks can greatly improve your application and save your memory.