← Back to Chapters

JavaScript Memory Management

? JavaScript Memory Management

? Quick Overview

JavaScript manages memory automatically. The engine allocates memory when you create values (numbers, strings, objects, functions, etc.) and frees it when those values are no longer needed. This automatic cleanup process is called garbage collection.

As a developer, you don’t manually free memory, but your code still strongly influences how efficiently memory is used. Poor patterns can cause memory leaks, leading to slow, bloated apps.

? Key Concepts

  • Memory allocation: Reserving memory space for variables, objects, and functions.
  • Stack & heap: Primitives and call frames live on the stack; objects live on the heap.
  • Reachability: Values reachable from roots (global variables, current call stack, closures) are kept.
  • Garbage collection (GC): Unreachable values are periodically cleaned up by the engine.
  • Memory leaks: Unwanted values kept in memory because references to them are never released.

? Syntax and Theory

Memory is allocated whenever you create new values:

  • Declaring primitives such as numbers, strings, and booleans.
  • Creating objects, arrays, and functions.
  • Calling a function (a new call frame is pushed on the stack with local variables).

JavaScript engines (like V8) use a reachability-based algorithm: anything that can be reached from the roots (global scope, current stack, and active closures) is considered “alive”. Anything else becomes eligible for garbage collection.

Memory problems typically arise when we accidentally keep references to data we don’t need anymore—for example through global variables, long-lived timers, or event listeners.

? Code Examples

? View Basic Memory Allocation Example
let num = 42;          // primitive number allocated on the stack
let str = "hello";     // string created and referenced
let obj = { a: 1 };    // object allocated on the heap
let arr = [1, 2, 3];   // array allocated on the heap
? View Garbage Collection by Reachability
let user = { name: "Alice" }; // user refers to an object in the heap
// later...
user = null;                  // original object is now unreachable
// When no other references exist, the GC will eventually reclaim this memory.
⚠️ View Example of a Memory Leak
const leaks = [];

function addLeak() {
  // Creates a large string and keeps pushing it into the global array
  leaks.push(new Array(1_000_000).join("x"));
}

setInterval(addLeak, 1000); // memory usage keeps growing over time
// Because 'leaks' is global and never cleared, nothing becomes unreachable.
?️ View Safer Pattern with Cleanup
let intervalId;

function startWork() {
  let counter = 0;

  intervalId = setInterval(() => {
    counter++;
    console.log("Tick:", counter);
  }, 1000);
}

function stopWork() {
  clearInterval(intervalId); // prevents the interval from running forever
  intervalId = null;         // reference cleared, eligible for GC
}

startWork();
// later, when work is done:
stopWork();
? View Example Using WeakMap
const metadata = new WeakMap();

function track(element, info) {
  metadata.set(element, info);
}

function showInfo(element) {
  console.log(metadata.get(element));
}

// If 'element' is removed from the DOM and has no other references,
// the entry in 'metadata' can be garbage-collected automatically.

? Live Behaviour and Explanation

? What Happens Over Time?

In the memory leak example, the setInterval callback keeps pushing large strings into the leaks array every second. Because leaks is a global variable and we never clear it, all those strings stay reachable. The garbage collector cannot free them, so memory usage grows continuously.

In contrast, when we call clearInterval() and set intervalId = null, the timer stops, and the interval’s internal references can be released. If no other references exist to objects used inside the callback, they become unreachable and the GC can reclaim their memory.

Tools like browser DevTools let you record heap snapshots, inspect detached DOM nodes, and watch the memory timeline. If memory keeps going up without dropping back down after activity, it usually indicates a leak.

? Debugging Memory and Use Cases

  • Use Heap snapshots to see which objects are retaining memory and how many references they have.
  • Use Performance/Memory profiles to detect long-running scripts and allocations that never get freed.
  • Use the Memory timeline to spot patterns of steadily increasing memory usage.
  • Apply WeakMap / WeakSet when you want to attach metadata to objects without preventing them from being garbage-collected.

? Tips & Best Practices

  • Clear setInterval and setTimeout when they are no longer needed.
  • Detach event listeners when removing DOM elements to avoid keeping nodes in memory.
  • Avoid unnecessary global variables; prefer block or function scope.
  • Be careful with closures that capture large objects or DOM nodes.
  • Use WeakMap and WeakSet for data that should not prevent garbage collection.
  • Explicitly set references to null when you are done with large objects and they are not needed anymore.
  • Regularly inspect memory usage in DevTools for long-running single-page applications.

? Try It Yourself

  • Create a memory leak using setInterval and a growing array. Open DevTools and observe the memory graph while it runs.
  • Write a closure that accidentally keeps a large object in memory. Then refactor the code to release the reference and verify the change with a heap snapshot.
  • Build a small DOM-based app where event listeners are attached to elements. Remove nodes from the DOM and verify that listeners are also removed to avoid leaks.
  • Experiment with WeakMap by attaching metadata to DOM elements and then removing those elements. Use DevTools to confirm that memory isn’t retained.