cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Generational collection & the Scavenger

Learn why JavaScript engines collect young objects quickly, copy survivors, promote long-lived data, and reserve major work for later.

By the end, you can
  • 01
    Explain the young-object shortcutDescribe why engines collect a young generation often while leaving long-lived objects alone.
  • 02
    Trace a copying collectionFollow roots, to-space, the scan index, survivor age, and promotion in a small Cheney-style model.
  • 03
    Compare collection workSeparate minor Scavenge collections from major Mark-Compact work and identify practical allocation choices.

Why objects die young

JavaScript programs create many small, temporary objects. A formatted message, an event payload, or an object literal inside a function often becomes unreachable before the next screen update. Engines exploit that common pattern instead of treating every object as equally likely to live for hours.

Definition

Generational collection divides the heap by age. It collects new objects often, moves survivors to an older area, and visits that older area less often.

This continues Mark-sweep & mark-compact. That lesson explains how a collector finds live objects. This lesson asks a simpler question first: where should it spend that work?

The generational hypothesis

The generational hypothesis is the observed pattern that most newly allocated objects become unreachable soon. It is a useful tendency, not a JavaScript guarantee. A cart can be long-lived; a message created while adding tea is usually short-lived.

A short-lived message beside a long-lived cartPop out in the code editor (opens in a new tab)JavaScript
const cart = []; function addTea() {  const message = { item: "tea", qty: 1 };  cart.push("tea");  return message.item;} console.log(addTea());console.log(cart.length);

Line 1 creates cart outside the function, so it can stay reachable for the shopping session. Line 4 creates message. Line 6 returns only its item text, so the message object can die when addTea returns. The code prints tea, then 1.

The word most matters. The engine does not know that a receipt is unimportant just because it has that name. It learns only from reachability and from the repeated pattern that many new values disappear before the next young collection.

Model three temporary receiptsPop out in the code editor (opens in a new tab)JavaScript
function makeReceipts(times) {  let diedYoung = 0;  for (let index = 0; index < times; index += 1) {    const receipt = { item: "tea", index };    diedYoung += receipt.item === "tea" ? 1 : 0;  }  return diedYoung;} console.log(makeReceipts(3));

Line 1 accepts the number of receipt-making turns. Line 3 makes one small receipt each turn. Line 5 counts it as a young death in this teaching model, and line 9 prints 3. This is a model count, not a browser heap measurement.

Real-life analogyDownloads folder and Photos album

Most files in a phone Downloads folder are opened once and deleted soon. The few you keep move into a Photos album. You can clean Downloads quickly and often, while the album needs attention less often.

In real life: A download used once
In JavaScript: A young temporary object
In real life: A saved photo
In JavaScript: A long-lived promoted object
In real life: Clean Downloads often
In JavaScript: Run minor collection often
In real life: Open Photos less often
In JavaScript: Visit old space less often

Where the analogy stops: Folders are visible choices. The engine uses reachability and collection policies, not your personal judgment.

Young and old generations

The young generation is the small area for new allocations. It is collected often because most objects there die quickly. The old generation holds objects that already survived young collections, so it is not scanned for every small burst of temporary work.

Put new and durable names in separate model spacesPop out in the code editor (opens in a new tab)JavaScript
const youngSpace = ["message", "receipt"];const oldSpace = ["cart"]; console.log("young " + youngSpace.length);console.log("old " + oldSpace.length);

Line 1 puts temporary names in the young-space array. Line 2 puts the durable cart in old space. Lines 4 and 5 print young 2 and old 1. The arrays are labels for a teaching model; JavaScript arrays do not choose a real heap generation.

The split is a cost decision. A frequent young collection can inspect a small area and leave a large, stable cart or app state alone. A small young space is cheap to clean because dead values are skipped and the few live values are copied.

This does not change JavaScript reachability. A young object can be live, and an old object can later be garbage. Age decides which collection usually looks first; root paths decide whether the object survives.

Semi-space copying and Cheney's algorithm

Semi-space copying reserves two young halves: from-space contains current objects and to-space receives survivors. Rather than freeing each dead message, the collector copies reachable survivors and discards the entire old half in one go.

Step through a young-generation Scavenge
Step 0 of 7Ready
Your turn: follow the blue line

This is a replay of an instrumented teaching model. It copies real model objects; it does not inspect your engine's heap.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const fromSpace = [  { id: "cart", age: 0, children: ["tea"] },  { id: "tea", age: 0, children: [] },  { id: "message", age: 0, children: [] },]; function scavenge(roots, fromSpace, oldSpace = []) {  const byId = new Map(fromSpace.map((item) => [item.id, item]));  const toSpace = [];  let scan = 0;  for (const id of roots) copy(id);  while (scan < toSpace.length) {    for (const child of toSpace[scan].children) copy(child);    scan += 1;  }  function copy(id) {    const item = byId.get(id);    if (!item || toSpace.some((copy) => copy.id === id)) return;    const next = { ...item, age: item.age + 1 };    if (next.age >= 2) oldSpace.push(next); else toSpace.push(next);  }  return { fromSpace: [], toSpace, oldSpace };} console.log(scavenge(roots, fromSpace).toSpace.map((item) => item.id).join(","));
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Cheney's algorithm is a simple breadth-first copying plan. It keeps a scan index over objects already copied to to-space. Each scanned survivor can reveal children that also need copying. The model increments age as it copies.

Forward one shared object only oncePop out in the code editor (opens in a new tab)JavaScript
const fromSpace = [  { id: "cart", child: "tea" },  { id: "tea", child: null },];const forwarding = new Map(); function copyOnce(id) {  if (forwarding.has(id)) return forwarding.get(id);  const address = "to-" + forwarding.size;  forwarding.set(id, address);  return address;} console.log(copyOnce("cart"));console.log(copyOnce("cart"));

Line 5 creates a forwarding map. Line 8 checks whether cart already has a to-space address. Line 9 makes an address only on the first copy. Lines 13 and 14 both print to-0, which shows why two references to one object do not create two copies.

The scan index is what makes the work breadth-first. First copy roots. Then scan the first copied object, copy its children, advance scan, and repeat until every copied object has been scanned. At that point every reachable young object is in the destination half.

Playground: change one young reference
Young copying modelJavaScript
const roots = ["cart"];const fromSpace = [  { id: "cart", age: 0, children: ["tea"] },  { id: "tea", age: 0, children: [] },  { id: "message", age: 0, children: [] },]; function scavenge(roots, fromSpace, oldSpace = []) {  const byId = new Map(fromSpace.map((item) => [item.id, item]));  const toSpace = [];  let scan = 0;  for (const id of roots) copy(id);  while (scan < toSpace.length) {    for (const child of toSpace[scan].children) copy(child);    scan += 1;  }  function copy(id) {    const item = byId.get(id);    if (!item || toSpace.some((copy) => copy.id === id)) return;    const next = { ...item, age: item.age + 1 };    if (next.age >= 2) oldSpace.push(next); else toSpace.push(next);  }  return { fromSpace: [], toSpace, oldSpace };} console.log(scavenge(roots, fromSpace).toSpace.map((item) => item.id).join(","));
Model output

to-space: cart,tea

old space: empty

Try it yourself

The next to-space contains cart and tea. The temporary message is never copied.

This lesson model copies reachable objects. It is not a browser heap inspector.
Real-life analogyTwo lunch boxes

When lunch box A is full, move only the food you will still eat into lunch box B. Then wash box A in one go. You do not need to scrub each empty spot separately.

In real life: Box A is full
In JavaScript: from-space is full
In real life: Move food you will eat
In JavaScript: Copy reachable survivors
In real life: Wash Box A at once
In JavaScript: Discard all old from-space
In real life: Check moved food for smaller food
In JavaScript: Scan copied children

Where the analogy stops: Food does not point to other food. Heap objects can reference more objects, which is why the scan index matters.

Promotion, tenuring, and pretenuring

Tenuring means tracking how many young collections an object survives. In this teaching model, an object moves to old space after its second survival. Real engines choose details differently, but the reason is the same: repeatedly copying a durable cart wastes young-collection work.

Promote a second survivorPop out in the code editor (opens in a new tab)JavaScript
const survivor = { id: "cart", age: 1 };survivor.age += 1;const destination = survivor.age >= 2 ? "old space" : "to-space"; console.log(destination);console.log(survivor.age);

Line 1 starts a cart at age 1. Line 2 records one more survival. Line 3 chooses old space because age is now two. Lines 5 and 6 print old space and 2.

Pretenuring means an engine may notice that one allocation site usually creates long-lived objects and allocate those objects directly in old space. For example, a site that builds the persistent cart state for every session may be a better old-space candidate than a receipt created for one click.

Model one allocation-site choicePop out in the code editor (opens in a new tab)JavaScript
function chooseSpace(survivesOften) {  return survivesOften ? "old space" : "young space";} console.log(chooseSpace(true));console.log(chooseSpace(false));

Line 1 asks one clear question about an allocation site. Line 2 returns old space when its objects usually survive. Lines 5 and 6 print old space and young space.

Pretenuring is not an API and it is not a promise. Application code should still make ownership clear, remove accidental retainers, and measure before changing allocation patterns. The engine can revise its allocation choice when the program changes.

Parallel mark-evacuate

Parallel scavenging lets several helper threads copy survivors at the same time when the engine can do so safely. V8 can use parallel mark-evacuate work too. The point is shared work, not a promise about one fixed number of threads.

Divide four survivor names between two helpersPop out in the code editor (opens in a new tab)JavaScript
const survivors = ["cart", "tea", "user", "note"];const helpers = [[], []]; for (const [index, id] of survivors.entries()) {  helpers[index % helpers.length].push(id);} console.log(helpers[0].join(","));console.log(helpers[1].join(","));

Line 1 names four survivors. Line 2 makes two work lists. Lines 4 and 5 give alternating names to the two lists. Lines 8 and 9 print cart,user and tea,note. This is a sequential JavaScript model of divided work, not a thread-count probe.

Real helpers need shared coordination. They must claim a survivor before copying it, publish its forwarding address, and keep checking for more work. That extra machinery prevents two helpers from moving the same object twice.

Parallel work can reduce elapsed collection time when enough survivors exist. It does not erase all pause costs, and it can add coordination overhead when the young collection is tiny.

Minor and major collections

A minor collection touches the young generation only. V8 calls this a Scavenge. It is frequent and usually fast because the young area is deliberately small and most objects are not copied.

Count a simulated collection runPop out in the code editor (opens in a new tab)JavaScript
const events = ["minor", "minor", "minor", "major", "minor"];const counts = { minor: 0, major: 0 }; for (const event of events) {  counts[event] += 1;} console.log("minor " + counts.minor);console.log("major " + counts.major);

Line 1 contains four minor events and one major event. Line 5 increases the matching counter. Lines 8 and 9 print minor 4 and major 1.

Step through minor and major counts
Step 0 of 6Ready
Your turn: follow the blue line

This replay counts model events. A real engine chooses collection timing based on current allocation pressure.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const counts = { minor: 0, major: 0 }; for (const event of events) {  counts[event] += 1;} console.log("minor " + counts.minor);console.log("major " + counts.major);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Collections that are easy to confuse
KindAreaTypical rhythmV8 name
Minor collectionYoung generation onlyFrequent and usually fastScavenge in V8
Major collectionWhole heap, including old spaceRarer and can do more workMark-Compact in V8
PromotionMove a survivor to old spaceAfter it survives enough young collectionsTenuring

Small V8 evidence

Node exposes V8 information for diagnosis. These snippets are Node-only, so they are not browser playgrounds. The lesson test asserts names and version families only; it never treats heap sizes or timings as stable facts.

List V8 heap space namesJavaScript
import v8 from "node:v8";
console.log(process.version);
console.log(process.versions.v8);
console.log(v8.getHeapSpaceStatistics().map((space) => space.space_name).join(","));
Create short-lived allocation pressure for --trace-gcJavaScript
const keep = [];
for (let index = 0; index < 30000; index += 1) {
  keep.push({ item: "tea", index });
}
console.log("kept", keep.length);

On Node 22 with V8 12.4, heap statistics include new_space and old_space. Running the second snippet with --trace-gc prints Scavenge lines. Those words are evidence of the concepts, not performance promises.

Practical allocation habits

Do not write app code around one assumed collection schedule. Keep temporary values local, avoid accidental global caches, and remove listeners or timers that retain data. A long-lived object is fine when it belongs to a clear owner.

Young, old, or collection work?
  • The message object inside addTea
  • A cart kept for the whole shopping session
  • Copy reachable young objects to to-space
  • An object that survived two young collections
  • A temporary object with no root path
  • Mark and compact across the whole heap
Try it yourself
0 of 6 correct

Sort each card by the role it has in this lesson's cart and message examples.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

  • “Young means unreachable.” Young means recently allocated. A young object can be live.
  • “Promotion means permanent.” Old objects can still become garbage and be collected later.
  • “Minor means no moving.” A copying Scavenge moves survivors.
  • “Pretenuring is an API.” It is an engine choice based on observed allocation behavior.

Practice exercises

Exercise 1 · Warm-upPredict the temporary example

Type the two output values from the tea example.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const cart = ["tea"];
function makeMessage() {
  const message = { item: "tea", qty: 1 };
  return message.item;
}
console.log(makeMessage());
console.log(cart.length);

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the copied survivor

    Which object reaches to-space?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const result = scavenge(["cart"], [
      { id: "cart", age: 0, children: [] },
      { id: "message", age: 0, children: [] },
    ]);
    console.log(result.toSpace.map((item) => item.id).join(","));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeName the destination

      Where does a survivor go after its second young collection in this lesson model?

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeChoose the pretenuring idea

        A report builder creates objects that stay for the entire session. What can the engine choose to do?

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengePick the frequent collection

          Which collection would you normally expect more often in a busy UI?

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeApply this to an app

            A typing experience feels slow. What should you do before changing every object literal?

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              Use the cart, message, and two-space model to keep the terms separate.

              Generational collection quiz · 6 questionsScore: first tries count
              1. Question 1 of 6What does the generational hypothesis say?

                Choose an answer to see the explanation.

              2. Question 2 of 6What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const cart = ["tea"];
                console.log(cart.length);

                Choose an answer to see the explanation.

              3. Question 3 of 6In semi-space copying, what happens to an unreachable young object?

                Choose an answer to see the explanation.

              4. Question 4 of 6What is Cheney's scan index for?

                Choose an answer to see the explanation.

              5. Question 5 of 6What is pretenuring?

                Choose an answer to see the explanation.

              6. Question 6 of 6Which statement compares collections accurately?

                Choose an answer to see the explanation.

              Key takeaways

              • Most new objects die young, so engines collect young space often.
              • Semi-space copying moves survivors and discards the whole old half.
              • Survivors may be promoted to old space; pretenuring can skip young space for likely durable objects.
              • Minor Scavenge work is young-only; major Mark-Compact work can cover the heap.

              Remember the one-liner.
              Generational GC makes the common short-lived object cheap to clean up.

              Coming next: Write barriers & remembered sets, where old objects point to young ones.

              CompleteFrontend Clear concepts. Working examples.