Memory Allocation, The Heap & Mark-and-Sweep Garbage Collection
Inspect the memory lifecycle. Explore primitive stack allocation, heap memory graphs, reference reachability, and V8's Mark-and-Sweep garbage collector.
By the end, you’ll be able to…
- Distinguish between Stack allocation (fixed size, fast) and Heap allocation (dynamic, graph-based).
- Understand reference reachability from roots (Global object, Call Stack).
- Explain V8's generational garbage collection (Orinoco, Scavenger, Mark-and-Sweep).
01The Three Stages of the Memory Lifecycle
Regardless of the programming language you use, the memory lifecycle follows three fundamental steps: allocate the memory you need, use the allocated memory (reading and writing values), and release the memory when it is no longer needed. In low-level languages like C and C++, developers must manually invoke malloc() and free() to manage this lifecycle. A forgotten free() causes memory leaks, while freeing too early causes catastrophic memory corruption.
JavaScript is a garbage-collected language. The engine automatically allocates memory whenever you create variables, objects, functions, or arrays, and it automatically releases that memory when those values are no longer reachable. However, 'automatic' does not mean 'magic': understanding how memory is partitioned between the Stack and the Heap is vital to building high-performance, non-leaking applications.
02The Stack vs The Heap
The JavaScript engine divides memory into two primary zones: the Call Stack and the Memory Heap. The Stack is used for static, contiguous memory allocation. Primitive values (numbers, booleans, undefined, symbols, null) whose size is fixed and known at compile time are stored directly within stack frames. Accessing stack memory is blazingly fast because the CPU merely increments or decrements the stack pointer.
The Heap is a large, unstructured region of memory dedicated to dynamic allocation. Objects, arrays, closures, and functions have arbitrary, variable sizes that can mutate at runtime. When you instantiate an object, V8 allocates a chunk of heap memory to hold the object's properties. It then stores the memory address (a reference pointer) on the Stack. When you assign an object to another variable, you are copying the pointer on the stack, not the underlying heap data.
03Mark-and-Sweep and Generational GC
How does V8 determine which heap allocations can be safely freed? Modern engines use the Mark-and-Sweep algorithm based on the concept of reference reachability. The collector defines a set of 'Roots': the global execution context, active call stack frames, and active DOM nodes. Periodically, the garbage collector starts at these roots and traverses every outgoing reference pointer, recursively marking every reachable object as 'alive'.
Once traversal concludes, the 'Sweep' phase begins: any heap memory allocation that was not marked as reachable is deemed garbage and reclaimed. To optimize this process, V8 employs Generational Garbage Collection: most objects die young. It divides the heap into a Young Generation (frequently collected via fast Scavenge algorithms) and an Old Generation (objects that survive multiple collection cycles and are collected via full Mark-Sweep-Compact cycles).
04Why This Matters in Production
Accidental memory leaks in client-side Single Page Applications (SPAs) and Node.js backend microservices almost always happen when developers inadvertently leave reachable references to objects that are no longer needed. Examples include forgetting to remove global event listeners, leaving active setInterval timers running, or retaining large arrays inside long-lived closures.
Because the root maintains a path of references to the object, Mark-and-Sweep considers it alive, preventing V8 from releasing it. Over hours or days, heap consumption steadily climbs until the container crashes with an Out-Of-Memory (OOM) error.
The ideas worth keeping.
- Primitive values reside on the Call Stack; dynamic objects and arrays are allocated on the Heap.
- Variables holding objects store reference pointers, meaning assignment copies the pointer, not the object.
- Mark-and-Sweep reclaims memory by traversing from roots; unrooted allocations are swept automatically.
Make it yours.
Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.
Exercise 1: Breaking Unreachable Object Cycles
Write a function that breaks circular references between two objects so they become eligible for garbage collection when the parent reference is removed.
Explore a worked solution
function unlinkCircular(nodeA, nodeB) { nodeA.neighbor = null; nodeB.neighbor = null;}Although modern Mark-and-Sweep handles circular references between unrooted objects, explicitly clearing cross-references ensures isolated nodes are collected promptly.
Exercise 2: Safe Cache with Weak References
Explain why using WeakMap instead of Map prevents memory leaks when caching metadata for dynamic object keys.
Explore a worked solution
// Using WeakMap allows keys to be garbage collected when no other references exist:const metadataCache = new WeakMap();function tagObject(obj, meta) { metadataCache.set(obj, meta);}WeakMap holds weak references to its keys. When an object key has no other live references in the application, V8 can garbage-collect it and its associated value automatically.
What stayed with you?
Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.
Exercise 1: Breaking Unreachable Object Cycles
Write a function that breaks circular references between two objects so they become eligible for garbage collection when the parent reference is removed.
[Process exited with code 0 - No output]
Keep exploring.
The official references behind this lesson, when you want a second angle or the full details.
Pause. Put the pieces together.
You’ve reached the end of How JavaScript Runs: Engines, Memory & Execution. Before moving on, use this task to check what you can do independently.
Correctly predict execution order, stack frames, and TDZ boundaries across sample programs.
Try it in your own environment and write down the result. This is a self-check; reading completion does not evaluate your code.
Review this moduleOne lesson closer.
Reading is a start. Trying it yourself makes the connection.