Why does JavaScript clean up after us, but we still need to understand how it works?
\n\nWhen you write in JavaScript, you rarely think about what happens to variables after they go out of scope. Unlike C or C++, where you must manually call free() or delete, JavaScript takes on the dirty work of memory cleanup. This is convenient — until the moment your application starts to "lag," consume gigabytes of RAM, or suddenly crash with an Out of Memory error.
Sound familiar? Then let's figure out how memory management in JavaScript actually works and what the JavaScript garbage collector is. Spoiler: it's not magic, but a strict algorithm that can (and should) be considered when writing code.
\n\nHow JavaScript stores data: stack and heap
\n\nBefore talking about garbage collection, you need to understand where data lives. JavaScript has two main storage areas:
\n\n- \n
- Stack — fast but limited memory. It stores primitives (numbers, strings, booleans) and references to objects. Each function call creates a new frame on the stack. \n
- Heap — an unstructured memory area where objects, arrays, functions, and closures live. This is where the main battle for memory takes place. \n
When you create an object:
\n\nlet user = { name: 'Ivan', age: 30 };\n\n\nThe reference user goes into the stack, and the object itself { name: 'Ivan', age: 30 } goes into the heap. As long as there is at least one reference to the object, it stays alive. Once there are no references left, the object becomes "garbage."
Garbage collector in JavaScript: the main character
\n\nThe JavaScript garbage collector (Garbage Collector, GC) is a background process that periodically checks the heap and removes unreachable objects. It sounds simple, but in practice it's a complex trade-off between cleanup speed and the performance of your code.
\n\nMark-and-Sweep algorithm
\n\nModern engines (V8 in Chrome, SpiderMonkey in Firefox) use the Mark-and-Sweep algorithm. It works in two stages:
\n\n- \n
- Mark: GC starts from root objects (global object, current functions, local variables). It traverses all references from the roots and marks every reachable object. \n
- Sweep: All unmarked objects are considered garbage and are removed. Memory is freed. \n
A simple example:
\n\nlet a = { data: 'A' };\nlet b = { data: 'B' };\na.ref = b; // a references b\nb.ref = a; // b references a\n\n// Now we have a circular reference\na = null; // a no longer points to the object\nb = null; // b no longer points to the object\n\n\nIn the past (before Mark-and-Sweep), such circular references led to leaks. But modern GC understands: if both objects are unreachable from the root, they are garbage, even if they reference each other.
\n\nGenerational objects: young and old
\n\nMost objects don't live long. For example, temporary variables inside a function. Therefore, V8 divides the heap into generations:
\n\n- \n
- Young Generation: new objects. Collection here happens frequently and quickly (Scavenge — an algorithm that copies live objects to another area). \n
- Old Generation: objects that have survived several collections. They are checked less often, but the process takes more time. \n
This approach prevents the page from "freezing" during a full heap check.
\n\nMemory leaks in JavaScript: how to cause them (and don't)
\n\nEven with a smart garbage collector, you can create a situation where memory is not freed. Here are the most common causes of leaks