Engrain by ByteRush

JS.12.03 Mid-level· 11 minCore — asked often

Garbage collection: how JavaScript frees memory you no longer use

Reachability, mark-and-sweep, V8's young and old generations, concurrent marking in Orinoco, and why GC pauses cause jank. Explained with a runnable demo.

Comes up in: JavaScript, Web fundamentals

Learn#

Sameer’s single-page app gets slower every hour. He set modal = null and expected the memory to come back, but a listener still pointed at the modal.

JavaScript has garbage collection: the engine frees memory for you. It keeps every object it can reach and frees the rest. Reachability means “can be found by following references from a root”, such as a global variable or a running function’s locals.

import { setFlagsFromString } from "node:v8";
import { runInNewContext } from "node:vm";
setFlagsFromString("--expose-gc");
const gc = runInNewContext("gc"); // lets this demo force a collection

let cart = { items: new Array(1_000_000).fill(0) };
const kept = { cart };            // a second reference
const ref = new WeakRef(cart);    // a reference that does not keep it alive
cart = null;
await new Promise((r) => setTimeout(r, 0));
gc();
console.log("still reachable:", ref.deref() !== undefined);
kept.cart = null;
await new Promise((r) => setTimeout(r, 0));
gc();
console.log("after removing the last reference:", ref.deref() !== undefined);
This example uses Node.js, so it runs in our checks rather than in your browser.

Output (verified)

still reachable: true
after removing the last reference: false

Setting one variable to null freed nothing while another reference remained.

Mark and sweep, starting from the rootsStep 1 of 4

Memory

  • root: window
  • root: current function's locals

Roots are where marking starts: global variables and the variables of functions still running.

Read the steps as text
  1. Roots are where marking starts: global variables and the variables of functions still running.
  2. Mark: follow every reference from the roots. app, user and cart are marked.
  3. oldModal and its listener point at each other, but nothing marked points at them.
  4. Sweep: every unmarked object is freed, including the cycle.

Predict#

Predict first

Two objects reference each other, and nothing else references either. Are they freed?

Interview angle#

Say it like this — about 30 seconds

“JavaScript frees objects that are no longer reachable from roots, such as globals and the current call stack. V8 marks everything reachable and sweeps the rest, so cycles are not a problem. It is generational: most objects die young, so new objects go in a small young space that is cleaned often and cheaply. Survivors move to the old space, which is marked mostly concurrently, off the main thread. Leaks happen when something reachable still points at data you no longer need.”

Stays on your device. Nothing is uploaded.
Follow-up Why are short-lived objects cheap?
The young-generation scavenger copies only the survivors; dead objects cost nothing to free.
Follow-up Can you force garbage collection in the browser?
No. Pages cannot trigger it; DevTools has a button, and Node.js has a --expose-gc flag for tests.
Follow-up Does setting a variable to null free memory?
Only if that was the last reference. It removes one path; the object goes when no path from a root remains.

Practice#

Guided

Write a tiny mark-and-sweep collector over a heap described as data.

Done when

  • Everything reachable from a root is kept, however deep.
  • An unreachable cycle is freed, and cycles never cause an endless loop.
  • Both lists are sorted, and every id appears in exactly one of them.
Your solution
Ctrl/⌘ + Enter

Remember#

Try to answer each card before you open it. Then add them to your reviews.

Which objects does the garbage collector free?

Objects not reachable from any root.

Does mark-and-sweep leak reference cycles?

No. Unreachable cycles are never marked, so they are freed.

What is the generational hypothesis?

Most objects die young.

Why do GC pauses matter in the browser?

Main-thread pauses delay input and frames, which users see as jank.

Go deeper

Four layers. Stop at the one your interview needs; each ends with a quick check.

The core ideaEvery round

Roots include globals, the variables of running functions, and things the engine holds, such as active timers and DOM listeners. Anything reachable from them stays alive Source: MDN: Memory management.

Check yourself: Name two kinds of GC roots.

Global variables and local variables of functions on the call stack.

How it works underneathSenior rounds
Mark-and-sweep visits every object reachable from the roots and marks it, then frees every unmarked object. V8 also compacts: it moves live objects together, so free memory is not left in small pieces.
Check yourself: What does the mark phase do?

Visits every object reachable from the roots and marks it as live.

Edge cases and trapsWhere people slip
Generational GC splits the heap. New objects go in a small young generation, cleaned often by the scavenger, which copies the few survivors. Objects that survive twice move to the old generation, which is collected less often by mark-compact Source: V8 blog: Trash talk — the Orinoco garbage collector.
Check yourself: In V8, where do new objects go first?

The young generation, cleaned by the scavenger.

In productionStaff-level answers
V8’s Orinoco does most marking on helper threads while JavaScript runs, and the rest in small steps, so main-thread pauses are short.
Pauses still happen. Creating many objects inside an animation frame or a scroll handler adds GC work; reuse objects in hot loops and check the Performance panel for GC events.
Check yourself: Which DevTools panel shows GC pauses on a page?

The Performance panel (look for GC events on the main thread).

Finished the five steps? Mark it, and its cards join your reviews.

Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.

Interview questions this answers

  • How does garbage collection work in JavaScript?