How to profile memory usage?
In the browser (Chrome DevTools)
1) Basic scenario: "is the heap growing?"
- Open DevTools -> Memory.
- Choose Heap snapshot -> Take snapshot (snapshot #1).
- Perform the suspicious actions (SPA navigation, opening/closing modals, lists, and so on).
- Take snapshot again (snapshot #2).
- Compare: the Comparison tab -> sort by Δ Size/Δ Count. Look for "growing" owners (classes/constructors), Detached DOM nodes, and large arrays/Maps.
What to look at:
- Retainers (retention chains) - show which reference is preventing GC.
- Distance - a small value means close to "root"; such objects are rarely released.
2) Real-time: "where objects are created"
Allocation instrumentation on timeline:
- Memory -> Allocation instrumentation on timeline -> Start.
- Reproduce the scenario for 10-30 seconds.
- Stop -> click through the graph's peaks -> look at the Allocation stacks (the code lines where allocations happen).
Pros: shows the hot spots of allocations "in place". Cons: noisy, but great for catching spikes.
3) Light mode
Sampling allocation profiler - less precise, but almost no overhead. Handy when it's hard to reproduce the bug for a long time.
4) Memory timeline
In the Performance tab, enable the Memory metric -> make a recording. If after a "manual GC" (the trash-can icon) the heap doesn't return to roughly its original level, there's a leak.
5) Quick checks from the console
// Current heap size (Chromium only):
performance.memory.usedJSHeapSize
// (Experimental) more detail per agent:
if (performance.measureUserAgentSpecificMemory) {
performance.measureUserAgentSpecificMemory().then(console.log);
}In Node.js
1) Inspecting via DevTools
Run with the inspector and open it in Chrome:
node --inspect index.js
# or when starting a debug session
node --inspect-brk index.jsIn Chrome -> chrome://inspect -> Open dedicated DevTools -> the Memory tab -> heap snapshots, just like in the browser.
2) Taking a heap snapshot at runtime
npm i heapdumpimport heapdump from 'heapdump';
setInterval(() => {
heapdump.writeSnapshot(`./heap-${Date.now()}.heapsnapshot`);
}, 60_000);Open the .heapsnapshot file in DevTools and compare.
3) Quick counters
setInterval(() => {
const m = process.memoryUsage();
console.log({
rss: Math.round(m.rss/1024/1024)+'MB',
heapUsed: Math.round(m.heapUsed/1024/1024)+'MB',
heapTotal: Math.round(m.heapTotal/1024/1024)+'MB',
ext: Math.round(m.external/1024/1024)+'MB'
});
}, 5000);If heapUsed keeps sawtoothing upward and never comes back down, that's a suspected leak.
4) GC traces (diagnostics)
node --trace-gc index.jsWatch the frequency and duration of pauses; a steady rise in "after GC" is a bad sign.
For SPA/React (and components in general)
1) The de-facto standard check protocol
- Open page A -> take Heap snapshot #1.
- Go to page B, come back to A (or open/close the component) -> #2.
- Click GC (the trash-can icon) -> #3.
- Compare #1 and #3. If page A's/the component's objects remain, and their count/size grows after repeats, that's a leak.
Look for:
- Detached DOM nodes;
EventListenerhandlers without a matchingremoveEventListener;- active
Timeout/Interval; - "zombie" subscriptions (WebSocket, RxJS);
- caches (
Map/Set) with no TTL/cleanup; - large structures in Context/Redux that nobody resets.
2) React specifics
-
Check
useEffectfor cleanup (a returned function):javascriptuseEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); // required! }, []); -
React DevTools Profiler: the component should Unmount when leaving the page.
-
Watch
refs to the DOM: after unmount they should becomenull.
A method for finding a leak (briefly)
- Pin down the symptom: "after N minutes the heap grows from X to Y".
- Narrow the scenario: the minimal reproducible sequence of actions.
- Gather data: a Heap snapshot before/after, the Allocation timeline, Perf Memory.
- Find the owner: the class/constructor where the growth happens -> check Retainers.
- Tie it to the code: the line/file, via Allocation stacks or sourcemaps.
- Fix it: add a cleanup / remove references / limit the cache.
- Verify: repeat steps 1-3 - the growth should disappear.
- Automate it (if possible): an e2e script that measures
usedJSHeapSizeat each step.
Common "tells" in reports
- A Detached HTMLDivElement grows -> a listener/reference to the DOM was never removed.
- Arrays/objects in a Map/Set grow -> there's no TTL/LRU cleanup.
- Lots of
Timeout/Interval-> noclearTimeout/clearInterval. - A pile of component instances after navigation -> it doesn't unmount, or closures/subscriptions "hold" it.
- A leak in a third-party library -> wrap it in your own adapter with cleanup, or replace it.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.