completefrontend
Open lab My progress
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 why new objects are checked oftenSay why engines check new objects often and leave lasting objects alone most of the time.
  • 02
    Follow one copying collectionTrace the roots, to-space, the scan number, an object's age, and its move to old space in a small example.
  • 03
    Tell the two kinds of collection apartTell a minor collection (Scavenge) from a major one (Mark-Compact), and pick good habits for your own code.

Why objects die young

JavaScript programs make many small objects that are only needed for a moment. Some examples:

  • a message built to show one line of text,
  • the details of one click,
  • an object made inside a function and used only in that call.

Soon, no part of the program can reach these objects any more. We say they are unreachable. An unreachable object is garbage, and the garbage collector frees its memory.

The engine is the program that runs your JavaScript, such as V8 in Chrome and Node. The engine keeps all objects in one memory area called the heap. Most objects in the heap die soon, so the engine treats new objects and old objects differently.

Definition

Generational collection splits the heap by age. It checks new objects often. It moves the objects that last into an older area. It checks that older area less often.

This lesson follows Mark-sweep & mark-compact. That lesson showed how a collector finds the objects that are still needed. This lesson asks a different question: which part of the heap should the collector check, and how often?

The generational hypothesis

To allocate an object means to make it and give it memory. The generational hypothesis is a pattern people have seen in real programs: most newly allocated objects become unreachable soon. It is a common pattern, not a rule of JavaScript. For example:

  • a shopping cart is often kept for a long time,
  • a message made while adding tea is usually needed only once.
A message that is needed once, beside a cart that lastsPop 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 makes cart outside the function. Code can still reach it after addTea ends, so it can last the whole shopping visit.
  • Line 4 makes message inside the function.
  • Line 6 returns only the text "tea", not the message object. When addTea ends, nothing can reach message any more.
  • Lines 9 and 10 print tea, then 1.

The word most matters. The engine does not know what an object is for. A name like receipt means nothing to it. The engine only knows two things:

  • whether an object can still be reached,
  • that most new objects die before the next check.
Count three receipts that are needed oncePop 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: times says how many times the loop runs.
  • Line 4 makes one small receipt object each time. Nothing keeps it after that loop turn.
  • Line 5 adds 1 to diedYoung for each receipt. In this lesson, "died young" means the object was not needed for long.
  • Line 10 prints 3.

This code only counts receipts to show the idea. It does not look at the real memory of your browser.

Real-life analogyDownloads folder and Photos album

Think of the Downloads folder on your phone. You open most files there once, and then you delete them. You move the few you want to keep into a Photos album. Downloads is small, so you can clean it quickly and often. The album needs tidying much less often.

In real life: A download you open once
In JavaScript: A new object that is needed once
In real life: A photo you keep
In JavaScript: An object that lasts and moves to the old area
In real life: Clean Downloads often
In JavaScript: Check the new objects often
In real life: Tidy the album rarely
In JavaScript: Check the old area less often

Young and old generations

A generation is a group of objects of about the same age. The heap has two of them:

  • The young generation, or young space, is a small area where new objects are made. The collector checks it often, because most objects there die quickly.
  • The old generation, or old space, holds objects that lasted through earlier checks of young space. The collector checks it less often.

The code below acts out the two spaces with two plain arrays.

Put new objects and a lasting cart in two pretend 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 the names of two new objects in the youngSpace array.
  • Line 2 puts the lasting cart in the oldSpace array.
  • Lines 4 and 5 print young 2 and old 1.

The arrays are only names for the idea. A real JavaScript array cannot choose where the engine keeps an object.

Two areas side by side. Young space holds message and receipt and is cleaned often. Old space holds cart and is cleaned less often. The code prints young 2 and old 1.young spacemessagereceiptcleaned oftenyoung 2old spacecartcleaned less oftenold 1
New objects such as message and receipt sit in young space, which is cleaned often. The cart that lasts sits in old space, which is cleaned less often.

Why split the heap at all? It saves work. A check of young space looks at a small area and leaves the big, lasting objects alone, such as the cart or the app's data. Young space is also cheap to clean: the collector skips the dead objects and copies only the few that are still needed.

Age does not change which objects are still needed. A young object can still be in use. An old object can later become garbage. Age only decides which area the collector usually checks first. Whether an object stays is still decided by whether the program can reach it.

Semi-space copying and Cheney's algorithm

Semi-space copying is a way to clean young space. "Semi" means half. Young space is split into two equal halves:

  • from-space holds the young objects right now,
  • to-space is empty and waits for the objects that are still needed.

The collector starts at the roots. A root is a place the program can always use, such as a global variable or a variable in a function that is still running. From the roots, it follows each link to find the objects that are still needed. It copies each one into to-space. An object that is copied is called a survivor. Then it throws away all of from-space in one go. It never has to free each dead object one by one.

A root tag points to cart in from-space. Cart points to tea. Cart and tea are copied to to-space. Nothing points to message, so it is not copied.rootfrom-spacecartteamessagecopiedto-spacecartteanot copied
The root reaches cart, and cart reaches tea, so both are copied to to-space. Nothing reaches message, so it stays behind and is thrown away with all of from-space.
Step through one check of young space
Step 0 of 7Ready
Your turn: follow the blue line

This replays the lesson's own copying code on small pretend objects. It does not look inside your browser's memory.

Running in
  1. script
Next: line 1
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(","));
CallStoreChangeResultBlue line: runs next. 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 plan for this copying, named after the person who wrote it down. It works like this:

  1. Copy the objects that the roots point to.
  2. Keep a number called scan. It marks the next copied object to look inside.
  3. Look inside that object. Copy any object it points to that is not copied yet.
  4. Add 1 to scan. Repeat until scan has passed every copied object.

This order is called breadth-first: the collector finishes the objects it already has before it goes deeper. In this lesson's code, each copy also adds 1 to the object's age.

Two links can point to the same object. The collector must copy it only once. So after the first copy it writes down the new place. That note is called a forwarding address.

Copy one object only once, even when two links reach itPop 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 makes a Map called forwarding. A Map stores pairs: here, an object's name and its new place.
  • Line 8 checks whether that name already has a new place. If it does, the function returns that place and copies nothing.
  • Line 9 makes a new place only on the first copy. It is "to-" plus how many objects were copied so far.
  • Lines 14 and 15 both print to-0. Two calls for cart give one copy, not two.

The scan number is what makes the work happen in order. When scan has passed every copied object, every young object the program can still reach is in to-space.

Playground: let the cart keep or drop tea
The copying code the playground runsJavaScript
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(","));
What the code copied

to-space: cart,tea

old space: empty

Try it yourself

To-space now holds cart and tea. Nothing points to message, so it is never copied.

This runs the lesson's copying code. It does not look at your browser's memory.
Real-life analogyTwo lunch boxes

You have two lunch boxes. Box A is full. You move only the food you will still eat into box B. Some of it is in bags, so you check each moved bag for more food to keep. Then you wash box A all at once. You do not clean each empty spot by itself.

In real life: Box A is full
In JavaScript: from-space is full
In real life: Move the food you will eat
In JavaScript: Copy the objects still needed
In real life: Wash box A all at once
In JavaScript: Throw away all of from-space
In real life: Check each moved bag for more food
In JavaScript: Look inside copied objects for more links

Promotion, tenuring, and pretenuring

Each object has an age: how many checks of young space it has survived. Tenuring means keeping track of that age. When an object is old enough, it moves to old space. That move is called promotion.

In this lesson's code, an object moves to old space when its age reaches 2. Real engines pick their own rules. The reason is the same everywhere: copying a lasting cart again and again inside young space is wasted work.

Move an object to old space once its age reaches 2Pop 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 makes a cart object with age 1. It has survived one check.
  • Line 2 adds 1 to the age: it survived one more check.
  • Line 3 picks "old space", because the age is now 2. The ? : works like a short if/else: if the test before ? is true, it gives the first value, otherwise the second.
  • Lines 5 and 6 print old space and 2.
Cart with age 1 sits in young space. It survives one more check of young space, so its age becomes 2 and it moves to old space.young spacecart, age 1survives againage goes up by 1old spacecart, age 2
The cart starts at age 1. It survives one more check of young space, and its age becomes 2. In this lesson's code, age 2 means it moves to old space.

An allocation site is the place in your code that makes an object, such as one line with an object literal. Pretenuring means the engine notices that one allocation site usually makes objects that last. So it makes those objects straight in old space and skips young space. For example:

  • the code that builds the cart for each visit may be a good fit for old space,
  • a receipt made for one click is not.
Pick a space for objects from one place in the codePop 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: survivesOften says whether objects from this place usually last.
  • Line 2 returns "old space" if they do, and "young space" if they do not.
  • Lines 5 and 6 print old space and young space.

Pretenuring is something the engine does by itself. Your code cannot ask for it, and the engine makes no promise. It can change its choice when your program starts to behave differently. Your job stays the same:

  • make it clear which code owns each lasting object,
  • remove links that keep objects alive by mistake,
  • measure before you change how your code makes objects.

Sharing the copying work

A thread is a worker inside the engine that can do a job at the same time as other workers. Parallel means "at the same time". Evacuate means move the objects that are still needed out of an area.

Parallel scavenging lets several helper threads copy survivors at the same time, when the engine can do it safely. V8 can also split its mark-evacuate work this way: it finds the objects still needed, then moves them. The point is sharing the work. There is no fixed number of helpers.

Share 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 lists four survivor names.
  • Line 2 makes two empty work lists, one for each helper.
  • Lines 4 and 5 hand out the names in turn: first helper, second helper, first, second. survivors.entries() gives each name with its position, index. index % helpers.length is the remainder after dividing by 2, the number of helpers, so it goes 0, 1, 0, 1.
  • Lines 8 and 9 print cart,user and tea,note.

This code runs one step at a time. It only shows how work can be split. It does not start any real threads.

Real helpers must work together carefully. Each helper must:

  • claim an object before it copies it,
  • write down the object's forwarding address,
  • keep checking for more work.

These steps stop two helpers from moving the same object twice.

Sharing the work can make a check finish sooner when there are many survivors. It does not remove every pause. When the check is tiny, the extra teamwork can even cost more than it saves.

Minor and major collections

A collection is one run of the garbage collector. There are two kinds:

  • A minor collection checks young space only. V8 calls it a Scavenge. It happens often and is usually fast, because young space is small on purpose and most objects in it are not copied.
  • A major collection can check the whole heap, old space too. V8 calls it Mark-Compact, the method from the last lesson. It happens less often and can do more work.
Count the minor and major collections in a pretend 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 lists four "minor" events and one "major" event.
  • Line 2 starts both counters at 0.
  • Line 5 adds 1 to the counter whose name matches the event.
  • Lines 8 and 9 print minor 4 and major 1.
Left: a minor collection checks young space and leaves old space alone. It happens 4 times. Right: a major collection checks both young space and old space. It happens 1 time.minor collectionyoung spaceold spacecheckedleft aloneminor 4major collectionyoung spaceold spacecheckedcheckedmajor 1
A minor collection checks only young space, so it is small and can run often. A major collection checks both spaces. In the example run there are 4 minor and 1 major.
Step through the minor and major counters
Step 0 of 6Ready
Your turn: follow the blue line

This replay counts made-up events. A real engine picks when to collect, based on how fast your code makes objects.

Running in
  1. script
Next: line 1
const counts = { minor: 0, major: 0 }; for (const event of events) {  counts[event] += 1;} console.log("minor " + counts.minor);console.log("major " + counts.major);
CallStoreChangeResultBlue line: runs next. 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.

The table puts three easy-to-mix-up words side by side. Read each row from left to right.

Minor collection, major collection, and promotion compared
WordWhat does it look at or do?How often?What does V8 call it?
Minor collectionIt checks young space only.Often. It is usually fast.V8 calls it a Scavenge.
Major collectionIt can check the whole heap, old space too.Less often. It can do more work.V8 calls it Mark-Compact.
PromotionIt moves a survivor from young space to old space.Once the object has survived enough checks of young space.It is part of tenuring: keeping track of an object's age.

What Node shows about V8

Node is a program that runs JavaScript outside the browser. It uses V8, the same engine as Chrome. Node can show some of V8's memory details, which helps when you look for problems.

The two snippets below only work in Node, so you cannot run them on this page. The lesson test checks only names and version numbers. It never treats memory sizes or times as fixed facts, because they change from machine to machine.

Print the names of V8's memory areas in NodeJavaScript
import v8 from "node:v8";
console.log(process.version);
console.log(process.versions.v8);
console.log(v8.getHeapSpaceStatistics().map((space) => space.space_name).join(","));
Make many small objects, then watch with --trace-gcJavaScript
const keep = [];
for (let index = 0; index < 30000; index += 1) {
  keep.push({ item: "tea", index });
}
console.log("kept", keep.length);
  • The first snippet asks V8 for its memory areas. On Node 22, which uses V8 12.4, the names include new_space (young space) and old_space (old space).
  • The second snippet makes 30,000 small objects. Run it with the --trace-gc option, which tells Node to print a line for each collection. You will see lines with the word Scavenge.

These words show that the ideas in this lesson are real in V8. They are not promises about speed.

Habits for your own code

Do not write app code that counts on collections happening at a certain time. Instead, follow these habits:

  • keep objects that are needed once inside the function,
  • do not add data to global caches by accident,
  • remove event listeners and timers that keep data alive when you no longer need them.

An object that lasts a long time is fine, as long as it is clear which part of the code owns it.

Young space, old space, or collector work?
  • The message object made inside addTea
  • A cart kept for the whole shopping visit
  • Copy the young objects still needed into to-space
  • An object that survived two checks of young space
  • A new object that nothing can reach
  • Mark and compact the whole heap
Try it yourself
0 of 6 correct

Put each card in the group that fits it best, using 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 only means the object was made recently. A young object can still be in use.
  • "Promotion means it lives forever." An object in old space can still become garbage. A later major collection frees it.
  • "A minor collection never moves objects." A Scavenge copies the survivors to a new place, so it does move them.
  • "Pretenuring is something my code can call." It is a choice the engine makes by itself, based on what it sees your code do.

Practice exercises

Exercise 1 · Warm-upPredict what the tea example prints

Type the two values this code prints, in order.

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 object that gets copied

    Which object ends up in 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 · PracticeSay where the object goes

      In this lesson's code, where does an object go after it survives its second check of young space?

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

        Exercise 4 · PracticePick what the engine can do

        A report builder makes objects that stay for the whole visit. What can the engine choose to do with the objects it makes?

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

          Exercise 5 · ChallengePick the collection that runs most often

          In a busy page with lots of clicks and typing, which collection would you expect to run more often?

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

            Exercise 6 · ChallengeUse this in a real app

            Typing in a text box feels slow. What should you do before you change every { } object in the code?

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

              Check your understanding

              Think about the cart, the message, and the two spaces as you answer. They help keep the words apart.

              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 a young object that nothing can reach?

                Choose an answer to see the explanation.

              4. Question 4 of 6In Cheney's algorithm, what is the scan number 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 sentence about minor and major collections is true?

                Choose an answer to see the explanation.

              Key takeaways

              • Most new objects die young, so the engine checks young space often.
              • Semi-space copying copies the objects still needed to to-space, then throws away all of from-space at once.
              • Objects that survive enough checks move to old space. With pretenuring, the engine can make lasting objects in old space right away.
              • A minor collection (V8: Scavenge) checks young space only. A major collection (V8: Mark-Compact) can check the whole heap.

              Remember the one-liner.
              Generational collection makes the many objects that are needed only once cheap to clean up.

              Coming next: Write barriers & remembered sets. It shows what happens when an object in old space points to an object in young space.

              CompleteFrontend Clear concepts. Working examples.