Memory management is very crutial in javascript for applications running on browsers or Nodejs. Unlike C/C++, in javascript memory gets allocated deallocated automatically using Garbage Collection mechanism.
In this blog, I will explain all the concepts with proper examples where you can actually relate.
Understanding how memory management can be done is needed in order to avoid memory leaks and performance optimization.
Understand Memory Life Cycle:
Memory life cycle follows 3 main steps (Irrespective of any programming language)-
1. Allocation of memory: Memory is allocated when objects, variables, functions are created.
2. Use allocated memory: Allocated memory is being used during execution (read/write) operations.
3. Deallocate (Release the) memory: Once it is no longer required, it will be released (Garbage collected
).
What is Garbage Collection 🗑️
JavaScript automatically manages memory using garbage collection (GC), meaning developers don’t need to manually allocate and free memory. However, understanding how it works helps prevent memory leaks and improve performance.
How Garbage Collection Works?
JavaScript uses the Mark-and-Sweep algorithm to clean up memory:
🔍 Mark → GC identifies objects still in use (reachable from the root, like window or global).
🧹 Sweep → Unreachable objects are deleted, freeing up memory.
GC runs automatically, but you can optimize memory usage by reducing unnecessary object references.
Types of Memory:
1. Stack Memory:
- It is used for primitive data types like boolean, string, number etc.
- Operates on a LIFO basis.
function firstFunction() {
let a = 10; // Stored in stack
console.log("Inside firstFunction");
secondFunction();
}
function secondFunction() {
let b = 20; // Stored in stack
console.log("Inside secondFunction");
thirdFunction();
}
function thirdFunction() {
let c = 30; // Stored in stack
console.log("Inside thirdFunction");
}
// Call firstFunction
firstFunction();
How Stack Works in This Example:
firstFunction()
is called → Execution context is pushed to the stack.
secondFunction()
is called inside firstFunction → New execution context is added.
thirdFunction()
is called inside secondFunction → Another execution context is added.
Execution completes from thirdFunction
→ It is popped from the stack.
secondFunction
finishes → It is popped from the stack.
firstFunction
finishes → It is popped from the stack.
Stack Flow (LIFO - Last In, First Out)
Call firstFunction()
Stack: [firstFunction]
Call secondFunction()
Stack: [firstFunction, secondFunction]
Call thirdFunction()
Stack: [firstFunction, secondFunction, thirdFunction]
thirdFunction() returns → Removed from Stack
Stack: [firstFunction, secondFunction]
secondFunction() returns → Removed from Stack
Stack: [firstFunction]
firstFunction() returns → Removed from Stack
Stack: []
Points to take-
1. Stack memory is used for function execution contexts and primitive values.
2. Functions are pushed when called and popped when finished (LIFO
).
3. Stack memory is fast but limited (can cause stack overflow with deep recursion).
2. Heap Memory: (Reference Data Types)
1. Used for Objects, Functions, Arrays.
2. This memory is allocated dynamically and accessed via references.
let obj1 = { name: "Shelja", age: 11 }; // Stored in heap
let obj2 = obj1; // obj2 points to the same memory location
obj2.age = 30;
console.log(obj1.age); // 30 (because both obj1 and obj2 reference the same object)
What happens here is { name: "Shelja", age: 11 }
stored in heap memory and both obj1 & obj2 holds the reference to the same object
.
Examples of some of the common memory issues -
1. Problem -
Accidentally declaring a variable without let, const, or var makes it a global variable, keeping it in memory throughout the app's lifetime.
function createLeak() {
leakedVar = "I am a memory leak"; // No `let`, `const`, or `var`
}
createLeak();
console.log(leakedVar);
// Exists globally, won't be garbage collected
Solution -
Always use let or const:
function createLeak() {
let leakedVar = "I am a memory leak"; // scope it properly
}
createLeak();
console.log(leakedVar);
2. Problem -
Timers keep references to objects, preventing them from being garbage collected.
function startLeak() {
let data = { value: "Leaking data" };
setInterval(() => {
console.log(data.value); // `data` is never cleared
}, 1000);
}
startLeak(); // Runs forever, even if `data` is no longer needed
Solution -
Always clear intervals when no longer needed.
function startSafe() {
let data = { value: "No leak" };
let interval = setInterval(() => {
console.log(data.value);
}, 1000);
// Clear interval after some time
setTimeout(() => {
clearInterval(interval);
data = null; // Remove reference
}, 5000);
}
startSafe();
3. Problem -
If you attach an event listener but never remove it, the function reference persists in memory.
function attachEvent() {
let button = document.getElementById("clickMe");
button.addEventListener("click", () => {
console.log("Clicked!");
});
}
attachEvent(); // If called multiple times, multiple listeners accumulate!
Solution -
Always remove event listeners when they’re no longer needed.
function attachSafeEvent() {
let button = document.getElementById("clickMe");
function handleClick() {
console.log("Clicked!");
}
button.addEventListener("click", handleClick);
// Remove listener when no longer needed
setTimeout(() => {
button.removeEventListener("click", handleClick);
}, 5000);
}
attachSafeEvent();
There are many more examples where we will find memory leak can happen and we need to more sure to prevent it to run applications smoothly and fast.
Key takeaways -
- Use Chrome DevTools’ Memory Profiler
(Performance > Memory)
to track memory leaks. - Mark-and-Sweep finds and removes unused objects from the heap.
-
Manually clear references
when an object is no longer needed.
Do like and comment if this was helpful :)