cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Incremental, concurrent & parallel GC

Learn how JavaScript engines split, overlap, and share garbage collection work to keep pages responsive while memory is reclaimed.

By the end, you can
  • 01
    Trace tri-color markingExplain white, grey, and black objects and why a write barrier protects the marking rule.
  • 02
    Compare scheduling choicesSeparate incremental slices, concurrent helper work, parallel work, safepoints, and idle-time work.
  • 03
    Read engine claims carefullyDescribe what Orinoco changed without promising a particular pause time or collection schedule.

Keeping collection responsive

A tracing collector must eventually inspect a large graph of objects. Doing every part in one long stop-the-world pause is simple, but a page cannot respond to typing, scrolling, or clicks while JavaScript is stopped. Modern engines therefore schedule the same basic work in smaller or overlapping pieces.

Definition

Incremental, concurrent, and parallel garbage collection are ways to schedule collection work: slices over time, helper work beside the app, and several helpers in one phase.

These words describe when and where collector work runs. They do not change JavaScript reachability. The engine still starts from roots, follows references, and preserves objects the program can reach. This lesson uses small models so the scheduling is visible without pretending to inspect your browser engine.

Four ways to schedule the same cleaning job
WordHouse cleaningWhat changes
Stop-the-worldClean the whole house while nobody uses it.One big pause.
IncrementalClean for ten minutes each evening.Several short pauses.
ConcurrentA helper cleans while you keep living there.Work overlaps with the app.
ParallelSeveral friends clean different rooms together.Several workers share one phase.

Tri-color marking

Tri-color marking is a checklist model for a tracing collection. White means not looked at yet. Grey means found, but its references have not all been opened. Black means the object and its outgoing references have been fully checked for this marking cycle.

A tiny tri-color teaching modelPop out in the code editor (opens in a new tab)JavaScript
const graph = {  user: ["cart"],  cart: ["items"],  items: [],};const colors = { user: "grey", cart: "white", items: "white" }; function markOne(name) {  for (const child of graph[name]) colors[child] = "grey";  colors[name] = "black";  console.log(name, colors.user, colors.cart, colors.items);} markOne("user");markOne("cart");markOne("items");

Lines 1 through 5 make user -> cart -> items. Line 6 starts user as grey because the marker has found it. Line 9 greys each child, and line 10 blacks the object after its drawer has been opened. The three logs print user black grey white, then cart black black grey, then items black black black.

Real-life analogyA checklist with drawers

Think of checking a room with a checklist. A name not looked at is white. A cupboard you found but have not opened is grey. A cupboard whose drawers are all checked is black.

In real life: Not checked on the list
In JavaScript: White object
In real life: Found, but its drawers remain closed
In JavaScript: Grey object
In real life: Every drawer has been checked
In JavaScript: Black object

Where the analogy stops: A real collector does not use literal paper colors. The colors are a compact way to describe its unfinished work.

The three colors are a work checklist
ColorMeaningWhy it matters
WhiteNot looked at yet in this marking cycle.It may be garbage unless marking reaches it.
GreyFound, but its outgoing references are not all checked.It waits on a marking worklist.
BlackFully checked in this marking cycle.It must not hide a white object from the marker.
Step through the three colors
Step 0 of 8Ready
Your turn: follow the blue line

Step through a small tri-color teaching model. It records real calls to the lesson function and its immutable color snapshots.

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
  user: ["cart"],  cart: ["items"],  items: [],};const colors = { user: "grey", cart: "white", items: "white" }; function markOne(name) {  for (const child of graph[name]) colors[child] = "grey";  colors[name] = "black";  console.log(name, colors.user, colors.cart, colors.items);} markOne("user");markOne("cart");markOne("items");
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.

The replay calls the same markOne function as the source. It records each color snapshot so you can step backward without seeing a later value early. It is a guided replay of a teaching model, not a debugger paused inside V8.

Incremental marking

Incremental marking splits one marking job into short slices. The main JavaScript thread marks some objects, returns to app work, then marks more later. Smaller slices can reduce the longest visible pause, although the collector needs extra bookkeeping between slices.

Mark two objects at a timePop out in the code editor (opens in a new tab)JavaScript
const work = ["user", "cart", "items", "price"];const sliceSize = 2;console.log(Math.ceil(work.length / sliceSize));

Line 1 names four pieces of work. Line 2 chooses two objects per pause. Line 3 prints 2, because four objects need two slices of two. Between those two model pauses, the page can handle other work. The engine does not promise that every real object costs the same amount to mark.

Playground: choose the marking slice size
Incremental marking modelJavaScript
const work = ["user", "cart", "items", "price", "coupon", "address"]; function markInSlices(sliceSize) {  let pauses = 0;  let longestPause = 0;  for (let start = 0; start < work.length; start += sliceSize) {    const slice = work.slice(start, start + sliceSize);    pauses += 1;    longestPause = Math.max(longestPause, slice.length);    // The app can run between these slices.  }  return { pauses, longestPause };} console.log(markInSlices(2).pauses, markInSlices(2).longestPause);
Modeled pauses3 pauses
pause 12 objects

user, cart

The app can run after this slice.

pause 22 objects

items, price

The app can run after this slice.

pause 32 objects

coupon, address

The app can run after this slice.

Try it yourself

With 2 objects per slice, this model has 3 pauses and a longest pause of 2 objects.

This is a teaching model. Real engines measure work by more than object count and choose slice timing dynamically.

Change only the slice size. A size of one produces more pauses but the shortest modeled pause. A size of six produces one pause for this six-object list. In a real engine, a scheduler uses time budgets and allocation pressure instead of simply counting object names.

Concurrent marking and bailout work

Concurrent marking lets a helper thread trace ordinary objects while the JavaScript thread continues running. That overlap can reduce main-thread work, but the app may change references while the helper is looking. This is why the write-barrier idea from the previous lesson returns here.

A marking barrier protects a new referencePop out in the code editor (opens in a new tab)JavaScript
const colors = { cart: "black", coupon: "white" }; function storeCartCoupon() {  // The app stores a white object into a black object.  colors.coupon = "grey";  console.log(colors.cart, colors.coupon);} storeCartCoupon();

Line 1 starts with a fully checked black cart and an unseen white coupon. Line 5 models an app write. The barrier on line 5 turns the coupon grey, and line 6 prints black grey. Without that repair, a black object could hide a live white child from the marker.

Step through a marking barrier
Step 0 of 4Ready
Your turn: follow the blue line

Replay the write-barrier teaching model. Real engines use optimized barriers, but the rule here is intentionally small.

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
 function storeCartCoupon() {  // The app stores a white object into a black object.  colors.coupon = "grey";  console.log(colors.cart, colors.coupon);} storeCartCoupon();
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.

Some things are awkward or unsafe for a helper to finish alone. A bailout worklist is a list of those objects: the helper records them, then the main thread handles them at an appropriate safe point. It is not a failure; it is a way to keep concurrent work simple and correct.

Leave difficult work for the main threadPop out in the code editor (opens in a new tab)JavaScript
const helperPile = ["cart", "items"];const bailoutWorklist = ["hostObject"];const mainThreadPile = []; while (bailoutWorklist.length) {  mainThreadPile.push(bailoutWorklist.shift());} console.log(helperPile.length, mainThreadPile.join(","));

Lines 1 and 2 separate normal helper work from one hard object. Lines 5 through 7 move that object to the main-thread pile. Line 9 prints 2 hostObject: two ordinary objects remain in the helper pile and the main thread receives the bailout item.

Worklists and work stealing

A marking worklist is the pile of grey objects that still need checking. With several helpers, each can keep its own pile. Local piles avoid constant coordination, because a helper can take its next object without asking every other helper first.

Give each helper a small pilePop out in the code editor (opens in a new tab)JavaScript
const helperOne = ["user", "cart", "items"];const helperTwo = [];helperTwo.push(...helperOne.splice(1));console.log(helperOne.join(","), helperTwo.join(","));

Line 1 starts one helper with three grey objects. Line 3 moves two into the empty helper's pile. Line 4 prints user cart,items. The precise split is an engine choice; the important point is that idle helpers can become useful again.

Work stealing is that balancing step. A helper that finishes early takes some work from a fuller pile. It does not mean user JavaScript moves between threads. It is collector workers sharing collector bookkeeping during a parallel phase.

Which scheduling idea is it?
  • Mark two objects, let the app run, then mark two more.
  • Several short pauses instead of one long pause.
  • A helper marks ordinary objects while JavaScript keeps running.
  • A helper leaves a difficult object for the main thread.
  • Two workers copy young objects into new space.
  • An idle worker takes half a busy worker's grey pile.
Try it yourself
0 of 6 correct

Sort each card by the scheduling idea it describes. The cards use the same names as this lesson.

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

Safepoints

A safepoint is a place where the JavaScript thread can pause and the engine can understand its live state. Think of a bus: it stops at bus stops, not in the middle of the road. A collector sometimes needs this cooperation for a short phase that cannot safely overlap arbitrary execution.

Pause at a planned stopPop out in the code editor (opens in a new tab)JavaScript
const steps = ["run handler", "bus stop", "run handler", "bus stop"];let paused = false; for (const step of steps) {  if (step === "bus stop") paused = true;} console.log(paused ? "paused safely" : "keep running");

Line 1 includes two planned bus stop entries. Line 5 changes paused only at those entries. Line 8 prints paused safely. This tiny model does not represent machine instructions; it shows why an engine chooses coordination points.

Safepoints explain why “concurrent” is not the same as “zero pauses.” Helpers can do a lot beside the app, while short coordinated pauses still establish a stable view, finish a phase, or move objects safely. The payoff is smaller main-thread interruptions, not an impossible promise of none.

Parallel scavenging and compaction

Parallel scavenging lets several workers copy young live objects at the same time. Parallel compaction lets several workers move objects and update references during a compacting phase. These phases use multiple cores to finish one collector job sooner.

Split copying work between two helpersPop out in the code editor (opens in a new tab)JavaScript
const youngSpace = ["cart", "items", "coupon", "address"];const helpers = [[], []]; youngSpace.forEach((name, index) => helpers[index % 2].push(name)); console.log(helpers[0].join(","), helpers[1].join(","));

Line 1 lists four young objects. Line 4 puts alternate names in two helper piles. Line 6 prints cart,coupon items,address. The model only divides names; real copying also installs forwarding information and updates every reference to moved objects.

Moving memory needs coordination because app code must not keep an old address. Engines use metadata, barriers, and safepoints to make the move invisible to JavaScript. Your variable still points at the same logical object after compaction, even though the engine changed where that object sits in memory.

Idle-time GC and Orinoco

Idle-time GC uses spare time between frames when the browser offers it. Like cleaning during a TV ad break, it is useful only when there is time left. Input, rendering, and network work can remove that spare time, so it is an opportunity rather than a deadline.

Use a small spare-time modelPop out in the code editor (opens in a new tab)JavaScript
const frame = { workMs: 11, budgetMs: 16 };const spareMs = frame.budgetMs - frame.workMs;console.log(spareMs >= 3 ? "run idle GC" : "wait");

Line 1 gives a frame 16 ms and says the page used 11 ms. Line 2 calculates 5 ms spare time. Line 3 prints run idle GC because five is at least the model's three-millisecond minimum. A busy frame would print wait instead.

Orinoco is V8's project name for work that made its collector mostly parallel and concurrent. It is not a JavaScript API and it does not guarantee one fixed behavior for every page. It names an engineering direction: move more collection work off the main thread when correctness allows.

About runtime evidence

Node can print Mark-Compact with --trace-gc, but whether incremental marking starts is scheduling-dependent. This lesson keeps its main claims model-based and does not turn one trace into a timing promise.

What this means for a page

Most application code does not choose a collector mode. It can still make the collector's job easier or harder by allocating large bursts, retaining unnecessary data, or doing heavy work during input and animation. The first move is to measure the page, not to guess from a GC term in a log.

Keep one frame smallPop out in the code editor (opens in a new tab)JavaScript
const frameBudget = 16;const appWork = 6;console.log(appWork < frameBudget ? "leave room" : "too busy");

Line 1 names a simple frame budget. Line 2 represents six milliseconds of app work. Line 3 prints leave room. This is not a rule that GC gets the other ten milliseconds; it is a reminder that rendering, input, and collection all compete for responsiveness.

Use a performance trace when scrolling or typing janks. Use heap tools when memory grows. Remove accidental roots such as unused listeners and unbounded caches. The next lesson, Weak references & ephemerons, explains special weak edges; do not use them as a substitute for clear ownership.

Common misconceptions

  • “Concurrent means no pause.” Helpers overlap work, but safepoints and finishing work can still pause JavaScript briefly.
  • “Parallel means my callbacks run at once.” It means collector workers share a GC phase, not that ordinary JavaScript loses its single-threaded rules.
  • “Grey means garbage.” Grey means discovered but unfinished; it is exactly the work that must be checked.
  • “Idle time is guaranteed.” It is spare time only when the browser has it.
  • “A trace proves the exact schedule.” Collection timing changes with allocation, device, and engine version.
Terms that sound alike but differ
TermWhat it meansDo not confuse it with
ConcurrentSome GC work runs alongside JavaScript.JavaScript never needs brief pauses or safepoints.
ParallelSeveral GC workers perform one phase together.The JavaScript language suddenly runs user code in parallel.
IncrementalOne job is split into small slices.The collector has no overhead or needs no barriers.
Idle-time GCWork uses spare time when the browser offers it.The browser promises a fixed amount of idle time.

The shared goal is responsiveness, but each tool solves a different constraint. Incremental work limits one pause, concurrent work overlaps the app, parallel work uses more workers, and idle-time work uses spare scheduling gaps. Engines combine them rather than selecting exactly one.

Practice exercises

Exercise 1 · Warm-upPredict the colors

Read the small model and type the two printed colors.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const colors = { user: "grey", cart: "white" };
colors.cart = "grey";
colors.user = "black";
console.log(colors.user, colors.cart);

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

    Exercise 2 · Warm-upCount the slices

    Predict the number of modeled marking pauses.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const work = ["user", "cart", "items", "price"];
    const size = 2;
    console.log(Math.ceil(work.length / size));

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

      Exercise 3 · PracticeRepair the black-to-white edge

      What color should the write barrier give the coupon?

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const colors = { cart: "black", coupon: "white" };
      if (colors.cart === "black" && colors.coupon === "white") colors.coupon = "grey";
      console.log(colors.coupon);

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

        Exercise 4 · PracticeName the fallback pile

        A helper cannot finish marking a host object. Name the list it should use.

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

          Exercise 5 · ChallengeChoose the safe stop

          Where can the JavaScript thread pause safely in this lesson's bus model?

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

            Exercise 6 · ChallengeApply this to a real page

            A search box sometimes janks on a real website. What should you measure before blaming garbage collection?

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

              Check your understanding

              For each question, identify whether it is about a color, a slice, a helper, or a planned coordination point.

              Incremental and concurrent GC quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In tri-color marking, what does grey mean?

                Choose an answer to see the explanation.

              2. Question 2 of 7What does this slice model print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const work = ["user", "cart", "items", "price"];
                const size = 2;
                console.log(Math.ceil(work.length / size));

                Choose an answer to see the explanation.

              3. Question 3 of 7Why does a marking barrier turn a new white child grey?

                Choose an answer to see the explanation.

              4. Question 4 of 7What is a bailout worklist for?

                Choose an answer to see the explanation.

              5. Question 5 of 7What does work stealing solve?

                Choose an answer to see the explanation.

              6. Question 6 of 7Why does a collector use a safepoint?

                Choose an answer to see the explanation.

              7. Question 7 of 7What does Orinoco name in V8?

                Choose an answer to see the explanation.

              Key takeaways

              • White is unvisited, grey is discovered but unfinished, and black is fully checked in a marking cycle.
              • Incremental marking splits work into pauses; smaller slices can reduce the longest pause.
              • Concurrent helpers need barriers and may leave special objects on a bailout worklist.
              • Parallel workers use worklists and stealing to divide collector work; safepoints coordinate short pauses safely.
              • Idle-time work uses spare frame time, while Orinoco is V8's mostly parallel and concurrent collector project.

              Remember the one-liner.
              Modern garbage collectors preserve reachability while carefully scheduling the work that proves it.

              Coming next: Weak references & ephemerons.

              CompleteFrontend Clear concepts. Working examples.