completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Allocation profiling

Learn to find which code allocates objects, estimate allocation volume, spot garbage churn, and measure browser memory safely.

By the end, you can
  • 01
    Find who makes memoryUse a timeline or sampling to tie new values to the function that made them.
  • 02
    Spot churnTell memory that keeps growing apart from many short-lived objects that still give the garbage collector extra work.
  • 03
    Measure with careCheck that the browser's memory function exists before you use it, and read a small Node sampling check.

What allocation profiling tells you

When your code makes a new object, array, or string, JavaScript has to find some memory to keep it in. Making a new value like this is called an allocation.

Allocation profiling means recording which code makes new values, and when. A profile is the report a tool gives you after it watches your code run. It answers the question "Which code made all this?" before you change any code.

An allocation can be:

  • an object, such as { text: "tea" };
  • an array;
  • a string;
  • a closure: a function that remembers the variables around it;
  • a value the engine makes for its own use.

This lesson looks at the ones you can see in your own code: object literals, arrays, and callbacks that make new values while an action runs.

The previous lesson used a heap snapshot. The heap is the part of memory where objects live. A snapshot is a picture of every object that is still kept right now. It answers a different question: "What is still in memory?"

Allocation profiling looks earlier, while values are being made. So it can tie a burst of new values to one click, one page update, one network request, or one scroll.

Definition

Allocation profiling finds the code that makes memory. A heap snapshot finds what still keeps memory. You often use both: first the profile, then the snapshot.

The previous lesson, Heap snapshots in depth, shows how to find what keeps an object alive. This lesson shows when a big number of short-lived values is itself the clue.

Count new objects in a small example

Start with a cart array that holds two items. The code below turns each item into a price label. It is small enough to count the new objects by hand, before you open any tool.

Make one price label per cart itemPop out in the code editor (opens in a new tab)JavaScript
const cart = [{ name: "tea", price: 3 }, { name: "bread", price: 4 }];const labels = cart.map((item) => ({ text: item.name + ": $" + item.price, price: item.price }));console.log(labels.length);

Line by line:

  • Line 1 makes the input array, with tea and bread.
  • Line 2 calls map. map runs a function once for each item and collects the results in a new array. The function you pass to it is called a callback.
  • Each time the callback runs, its object literal (the part in ({ ... })) makes one new label object.
  • Line 3 prints 2, because the new array holds two labels.

map does not change or copy the cart. The callback runs once for tea and once for bread. Each run makes one fresh object. This is normal, useful work. It is not a bug.

Left: the cart array holds two items, tea 3 and bread 4. An arrow marked map points to a new array called labels, which holds two new label objects, tea $3 and bread $4.tea: 3bread: 4cart2 items inmaptea: $3bread: $4labelsnew array + 2 labels
map leaves the cart as it is. It makes a new array, plus one new label object for each item: three new objects in all.
Step through: map makes two labels
Step 0 of 4Ready
Your turn: follow the blue line

This replays the lesson's own example code. It is not a view inside the JavaScript engine.

Running in
  1. script
Next: line 1
 function makePriceLabels(items) {  return items.map((item) => ({ text: item.name + ": $" + item.price, price: item.price }));} const labels = makePriceLabels(cart);console.log(labels.length);
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 step-through replays this lesson's own code. It is not a view inside the JavaScript engine. It counts only the objects our example asks for. A real profile shows much more, such as values the engine makes for its own use.

Allocation timelines: when memory is made

An allocation timeline lists each allocation in the order it happened, along a line of time. Use it when one action starts the problem. It lets you see the burst right next to that action.

For example, one click on a checkout button might:

  • build price labels;
  • draw a receipt on the page;
  • update the number on the cart icon.

A timeline shows whether the label work happened during the click, or whether some later task made the burst.

Count which function made each allocationPop out in the code editor (opens in a new tab)JavaScript
const owners = ["makePriceLabels", "renderCart", "makePriceLabels", "makePriceLabels"];const totals = {};for (const owner of owners) {  totals[owner] = (totals[owner] || 0) + 1;}console.log(totals.makePriceLabels);
  • Line 1 lists four pretend allocations, in time order. Each entry is the name of the function that made it.
  • Line 3 goes through the list from first to last.
  • Line 4 adds one to that name's count.
  • Line 6 prints 3, because makePriceLabels appears three times.

This small counter is a teaching model, not DevTools. DevTools is the set of developer tools built into the browser. A real timeline also records the exact time of each allocation and the functions that were running. The model keeps only the order. That is enough to spot a name that repeats.

Four cards in time order: makePriceLabels, renderCart, makePriceLabels, makePriceLabels. The three makePriceLabels cards are marked 1st, 2nd and 3rd. An arrow under them runs from earlier to later.makePriceLabelsrenderCartmakePriceLabelsmakePriceLabels1st2nd3rdearlierlater
Each card is one moment when new memory was made, in the order it happened. makePriceLabels shows up three times, so the code prints 3.
Step through: count allocations in time order
Step 0 of 4Ready
Your turn: follow the blue line

This replays the lesson's timeline model. It is not a recording from a browser engine.

Running in
  1. script
Next: line 1
const result = recordAllocations(owners);console.log(result.totals.labels);
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.

Start with one action you can repeat exactly. Press the same button, type the same search text, or open the same page. Then the next recording is easy to compare with this one.

Values your code can no longer reach are called garbage. The garbage collector (GC) is the part of the engine that finds garbage and frees its memory.

Real-life analogyPaper cups and churn

Using one paper cup is not a problem. Using many cups quickly fills the bin, even if you only hold one cup at a time. Someone still has to empty the bin.

In real life: You use one paper cup for a short drink
In JavaScript: One short-lived object is soon not needed
In real life: Many cups fill the bin
In JavaScript: Many allocations leave a lot of garbage
In real life: Someone empties the bin
In JavaScript: The GC frees values nothing can reach

Allocation sampling: a lighter guess

Allocation sampling writes down only some allocations and uses them to guess the full pattern. For example, it might note every second allocation and skip the rest.

Because it records less, it slows the page down less. That makes it a good fit for a longer session. The cost is that you no longer get a full list of every allocation.

Sampling is useful when you don't need the exact moment of each object. For example, you may only want to know which of these jobs makes the most short-lived values:

  • drawing labels;
  • formatting prices;
  • reading answers from the server.
Step through: keep every second allocation and guess the total
Step 0 of 4Ready
Your turn: follow the blue line

This replays the lesson's sampling model. A real sampling profile gives an estimate, not an exact list of every allocation.

Running in
  1. script
Next: line 1
const result = sampleAllocations(owners, 2);console.log(result.estimatedTotal);
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 step-through uses six pretend allocations and keeps every second one. It keeps three and guesses 3 × 2 = 6 allocations in total. The guess is exact here only because the input is perfectly even. A real profile gives an estimate: a close guess, not an exact count.

Six cards in a row: label, label, render, label, label, render. Arrows mark the 2nd, 4th and 6th cards as kept. Below: 3 kept times 2 equals 6.labellabelrenderlabellabelrenderkeptkeptkept3 kept × 2 = 6
With every = 2, only every second allocation is written down: label, label, render. Three kept allocations times two gives a guess of six.
Playground: change how often an allocation is kept
Keep every second allocation, then guessJavaScript
const owners = ["label", "label", "render", "label", "label", "render"];const every = 2;const sampled = owners.filter((_, index) => (index + 1) % every === 0);console.log(sampled.join(","));console.log(sampled.length * every);
What was kept

Kept: label, label, render, render

Guessed total: 8

Real total: 8

Try it yourself

Keep every 2: 4 kept × 2 = a guess of 8. The playground really has 8 allocations.

The playground uses eight allocations: the six in the code, plus label and render. A bigger number keeps fewer and makes a rougher guess.

In the playground, change the number in the box from 2 to 3 or 4. The playground uses eight pretend allocations. Compare the estimated total with those eight. A bigger number records fewer allocations. The profiler gets lighter, but the guess gets rougher.

Real-life analogyChecking every tenth bill

A shop owner checks every tenth bill instead of every bill. They can still learn which items sell the most. Checking fewer bills saves time, and the owner accepts that some bills go unchecked.

In real life: A shop owner checks every tenth bill
In JavaScript: A profiler writes down one allocation out of every few
In real life: Items people buy a lot still show up often
In JavaScript: Functions that allocate a lot still show up in the samples
In real life: Some bills are never checked
In JavaScript: Some single allocations are not in the samples

Pick the right DevTools view

Timelines and sampling answer two questions that sit close together. A heap snapshot answers a third one. A leak is memory that stays kept after it should have been freed.

Pick the view first. That avoids two common mistakes:

  • using a snapshot, which shows what is kept, to explain a short burst;
  • using a sample to prove that one object leaked.

Read each row as: the view, what it shows, and when to pick it.

Three memory views and when to use each
ViewWhat does it show?When should you use it?
Allocation timelineIt shows each allocation in time order, while you record.Use it when one action starts a burst and you need to know when.
Allocation samplingIt writes down some allocations and links each to the function that made it.Use it for longer runs, when you want a light guess at which function makes the most.
Heap snapshotIt shows the objects still kept at one moment.Use it once you know memory keeps growing.

Pick a timeline when one click or page change starts the problem and timing matters. Pick sampling when the work runs longer and you want a cheap ranking of the functions most likely to allocate.

Pick a heap snapshot only when memory is still kept after the action should be finished. A snapshot follows the chain of objects that keeps a value alive. It does not replay the burst that made those values.

Churn: many short-lived objects

Churn means making a lot of short-lived objects. It is different from a leak:

  • A leak keeps memory reachable, so the memory in use keeps growing.
  • Churn may leave the memory in use flat, because the objects die quickly. But the garbage collector still has extra work to do.

That extra work for the garbage collector is called GC pressure.

A new label object on every turnPop out in the code editor (opens in a new tab)JavaScript
const labels = [];for (let index = 0; index < 3; index += 1) {  labels.push({ text: "item " + index });}console.log(labels.length);
  • Line 2 starts a loop that runs three times.
  • Line 3 makes one new object on each turn and adds it to the array.
  • Line 5 prints 3: the number of label objects this small program made.

The array keeps the labels on purpose, so you can count them. In a real churn problem, short-lived labels may be thrown away after one page update. But they still had to be made. And the garbage collector still had to find them later.

One object, reused on every turnPop out in the code editor (opens in a new tab)JavaScript
const labels = [];const reusable = { text: "" };for (let index = 0; index < 3; index += 1) {  reusable.text = "item " + index;  labels.push(reusable.text);}console.log(labels.length);
  • Line 2 makes one object to reuse.
  • Line 4 changes its text instead of making a new object.
  • Line 5 adds that text, a string, to the array.
  • Line 7 prints 3, because the array holds three strings. Only one object was made to hold the label text.
Left: a new object on each turn. Three cards, item 0, item 1 and item 2, so 3 objects are made. Right: one reused object. A box called reusable holds one card, item 2, so only 1 object is made.new each turnitem 0item 1item 23 objects madeone, reuseditem 2reusable1 object made
Both loops run three times. The first makes a new object each time. The second makes one object and changes its text, so at the end it holds item 2.

Reusing one object is not always better:

  • It can make code harder to follow.
  • It can keep old data around by mistake.
  • The saving can be too small to measure.

Compare the two ways only after a profile points to a hot path: code that runs very often, where small savings add up.

Each row below is one way code can use memory. It says what happens and when the evidence matters.

Four ways code uses memory
PatternWhat happens?When does it matter?
New object in a loopIt makes one fresh object on every turn.This is fine when the code is clear and a profile shows no real problem.
Reuse one objectIt changes the fields of one object that already exists.Try it only for code that runs very often, after a profile points there.
Cache that keeps valuesIt keeps values reachable from one action to the next.Check it with a heap snapshot when the cache should have let data go.
Pretend clean-up modelIt frees waiting garbage every few turns, at a time we pick.It is only for teaching. Real engines pick their own clean-up times.

The next code uses a helper from this lesson, modelCollectionPressure, so it is shown to read, not to run. The helper makes seven pretend allocations. It runs a pretend clean-up every three turns.

A pretend model of garbage that waitsJavaScript
const report = modelCollectionPressure(7, 3);console.log(report.allocations);console.log(report.collections.join(","));console.log(report.garbageWaiting);

It prints three lines:

  • 7: the number of allocations;
  • 3,3: each of the two clean-ups freed three objects;
  • 1: one object is still waiting for the next clean-up.

This is only a model. Real browsers decide on their own when to collect garbage.

Measure a page's memory

performance.measureUserAgentSpecificMemory() is a browser function that measures how much memory the page uses. It returns a promise: a value that arrives later. When it arrives, the report holds:

  • bytes: the total number of bytes;
  • breakdown: a list that splits the total into parts.

Use it to compare the same page, in the same state, over time. One number alone is not a memory budget.

Checking that a feature exists before you use it is called feature detection. The code below does that first.

Check that the browser has the memory function firstPop out in the code editor (opens in a new tab)JavaScript
async function showMemory() {  const api = performance.measureUserAgentSpecificMemory;  if (typeof api !== "function") {    console.log("not available here");    return;  }  const report = await api.call(performance);  console.log("total bytes", report.bytes);  console.log("breakdown entries", report.breakdown.length);} showMemory();
  • Line 2 reads the function. It may be missing.
  • Lines 3 to 5 print not available here and stop if the function is missing.
  • Line 7 waits for the report. api.call(performance) runs the function on performance, the same as writing performance.measureUserAgentSpecificMemory().
  • Line 8 prints the total bytes, and line 9 prints how many parts the breakdown has.

The browser only allows this function on a page that is:

  • a secure context: served over HTTPS, or from your own computer (localhost);
  • cross-origin isolated: a locked-down page. Windows from other sites can't keep a link to it, and files it loads from other sites must agree to be loaded.

The server turns on cross-origin isolation with two response headers, called COOP (Cross-Origin-Opener-Policy) and COEP (Cross-Origin-Embedder-Policy). They are security settings sent with the page. JavaScript can't switch them on from inside a function. Read Browser security model for the full rules.

Feature detection matters, because:

  • a browser may not have the function;
  • a local preview may not be cross-origin isolated;
  • a page shown inside another page may have different permissions.

When the function is missing, keep going with allocation profiles and snapshots. Don't make up a byte count.

A real sampling check in Node

Node is a program that runs JavaScript outside the browser. It has a built-in Inspector: a way for tools to talk to the engine and ask it for profiles.

The code below uses the Inspector to ask for a sampling profile. It only runs in Node, so it is shown here to read, not to run in the browser.

Ask Node for a sampling profileJavaScript
import { Session } from "node:inspector"; function makePriceLabels() {  return Array.from({ length: 1200 }, (_, index) => ({    text: "tea: $" + index,    details: "label " + index + " for the cart",  }));} const session = new Session();session.connect();const post = (method, params = {}) => new Promise((resolve, reject) => {  session.post(method, params, (error, result) => error ? reject(error) : resolve(result));}); await post("HeapProfiler.enable");await post("HeapProfiler.startSampling", { samplingInterval: 256 });const retainedLabels = Array.from({ length: 12 }, makePriceLabels);const profile = await post("HeapProfiler.stopSampling");const names = new Set();const visit = (node) => {  names.add(node.callFrame.functionName);  for (const child of node.children || []) visit(child);};visit(profile.profile.head);console.log("found makePriceLabels", names.has("makePriceLabels"));console.log("retained labels", retainedLabels.length);session.disconnect();
  • Lines 3 to 8 define makePriceLabels. Each call makes 1,200 label objects.
  • Lines 10 to 14 connect to the Inspector. post sends it one command and waits for the answer.
  • Lines 16 and 17 turn on heap profiling and start sampling. samplingInterval: 256 asks for about one sample for every 256 bytes made, so small objects are likely to be seen.
  • Line 18 calls makePriceLabels twelve times and keeps all the labels until sampling stops. That makes the function very likely to show up.
  • Line 19 stops sampling and gets the profile. The profile is a tree: each part names a function, and its children are the functions it called.
  • Lines 20 to 25 walk the tree and collect every function name.
  • Line 26 prints whether makePriceLabels was found.

The lesson test checks that true-or-false answer. It does not check a byte count, a memory address, or a time, because those change from run to run.

This proves one small, real fact: Node's sampling profile names the function that made the objects. It does not mean browser and Node profiles look the same. It also does not mean every allocation from this function is a problem.

A practical profiling routine

Start with a problem a user can notice, such as:

  • a slow search box;
  • scrolling that stutters;
  • a page that gets heavier on each visit;
  • a server request that makes a surprising number of short-lived values.

Then follow these steps:

  1. Repeat one action.
  2. Record an allocation view.
  3. Name the function that most likely makes the values.
  4. Decide what it is: normal work, churn during an action, or a lead to check with a heap snapshot.
Choose the right memory tool
  • Find the new values made during one checkout click.
  • Find which functions likely make the most values in a long session.
  • Find why a cart array is still kept after you leave the page.
  • See whether scrolling makes bursts of new values again and again.
  • Guess which function makes the most objects overall.
  • Compare what is still kept before and after ten actions.
Try it yourself
0 of 6 correct

Sort each task by the view that answers its first question.

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

Change one cause at a time. For example, remove one step that makes the same values twice. Then record again and compare the same action. Don't swap every object literal for one shared object before you know allocation is the problem.

Common mistakes

Each memory tool has its own job. Keep their questions apart. Then a profile is easier to explain, and you are less likely to make a clever change that doesn't help.

  • "Lots of allocations means a leak." A leak keeps objects alive. Objects from churn may be freed soon after they are made.
  • "Sampling is wrong because it misses events." Sampling skips allocations on purpose, so it slows the page less. It still gives a good estimate.
  • "One byte count is a budget." Compare the same action, on the same setup, over time.
  • "Reusing objects always wins." Reuse can make code harder to follow and keep old data alive longer.

A profile is evidence for one question. It is not a final answer. Pair it with an action you can repeat and a simple guess you can test. "This cart code makes new labels on every key press" is much more useful than "memory looks high."

Practice exercises

Exercise 1 · Warm-upPredict a label count

Read the cart and the map call before you run it. Type the exact number it prints. Then say why the callback runs that many times.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const cart = [{ name: "tea", price: 3 }];
const labels = cart.map((item) => ({ text: item.name, price: item.price }));
console.log(labels.length);

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

    Exercise 2 · Warm-upGuess the sampled total

    This sampling example is perfectly even. Guess the estimate before you run it. Then compare the number of kept events with the total number of events.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const owners = ["label", "render", "label", "render"];
    const sampled = owners.filter((_, index) => (index + 1) % 2 === 0);
    console.log(sampled.length * 2);

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

      Exercise 3 · PracticeCount the new objects

      Count how many objects are made, not just the final number on screen. Type how many label objects this loop makes. Then say where the object literal runs.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      let created = 0;
      for (let index = 0; index < 3; index += 1) {
        const label = { text: "item " + index };
        created += 1;
      }
      console.log(created);

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

        Exercise 4 · PracticePick a view

        A checkout click makes a burst of new values only while the receipt is drawn. Name the first DevTools view you would record. Then say what question that view answers.

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

          Exercise 5 · ChallengeName the page setting

          On a local preview, the memory example prints not available here. Name the page setting to check before you call this a bug in your app.

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

            Exercise 6 · ChallengeApply it to a real app

            A product page feels slow after you type in its search box. Write the first thing you would do. Do it before you rewrite the code that builds the results, and before you add a shared cache that reuses objects.

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

              Check your understanding

              For each question, first decide which of three things it asks:

              • who made the memory;
              • when a burst happened;
              • what is still kept.

              The words sound close, but each tool answers a different one.

              Allocation profiling quiz · 8 questionsScore: first tries count
              1. Question 1 of 8What new values does map make in the price-label example?

                Choose an answer to see the explanation.

              2. Question 2 of 8What does this print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                console.log([{ text: "tea" }, { text: "bread" }].length);

                Choose an answer to see the explanation.

              3. Question 3 of 8Why is allocation sampling useful?

                Choose an answer to see the explanation.

              4. Question 4 of 8What is churn?

                Choose an answer to see the explanation.

              5. Question 5 of 8What does measureUserAgentSpecificMemory() return?

                Choose an answer to see the explanation.

              6. Question 6 of 8Which tool best explains why an object is still kept after you leave a page?

                Choose an answer to see the explanation.

              7. Question 7 of 8The pressure model makes seven allocations and cleans up after every three. How many are still waiting after the second clean-up?

                Choose an answer to see the explanation.

              8. Question 8 of 8What does this reused object print?

                Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
                const buffer = { text: "" };
                buffer.text = "tea";
                buffer.text = "bread";
                console.log(buffer.text);

                Choose an answer to see the explanation.

              Key takeaways

              • An allocation profile shows who makes values. A heap snapshot shows what is still kept.
              • A timeline keeps the order of every allocation. Sampling writes down only some allocations and guesses the rest, so it slows the page less.
              • Churn, many short-lived objects, can give the garbage collector extra work even when nothing leaks.
              • Check that the browser memory function exists before you use it. It needs a cross-origin isolated page.
              • Measure first. Then fix the code that makes the values behind the user's problem.

              Remember the one-liner.
              Allocation profiling asks who makes memory. A heap snapshot asks what still keeps it.

              Coming next: Memory in large apps.

              CompleteFrontend Clear concepts. Working examples.