Performance profiling in the browser matters once your pages get past the basics. Finding slow code, layout thrash, and memory leaks before users complain. Guessing is slower than measuring.
DevTools Performance panel
Record a session while you reproduce the jank: scroll a long list, open a panel, submit a form. The flame chart shows where time went – JavaScript, layout, paint, idle.
- Long yellow blocks in scripting – optimise JS or move work to a worker.
- Purple layout spikes – you are reading layout after writing DOM repeatedly.
- Green paint – large repaints; simplify DOM or promote layers sparingly.
Throttle CPU and network in DevTools to mimic real devices. A desktop that flies can hide problems that show on a phone.
Performance marks and measures
performance.mark('render-start');
renderTable(rows);
performance.mark('render-end');
performance.measure('table-render', 'render-start', 'render-end');
const entry = performance.getEntriesByName('table-render').at(-1);
console.log(`Render took ${entry.duration.toFixed(1)}ms`);
Marks show up in Performance recordings. Use them to compare before/after when you optimise a hot path.
Avoiding layout thrash
// bad - interleaved read/write forces sync layout
items.forEach((item) => {
const row = document.createElement('tr');
row.textContent = item.name;
tbody.appendChild(row);
const height = tbody.offsetHeight; // forces layout every loop
});
// better - batch writes, read once
const frag = document.createDocumentFragment();
items.forEach((item) => {
const row = document.createElement('tr');
row.textContent = item.name;
frag.appendChild(row);
});
tbody.appendChild(frag);
const height = tbody.offsetHeight;
The intermediate DOM performance tutorial covers batching in more detail – same rules apply here with profiling to prove the win.
Memory panel and heap snapshots
Take a heap snapshot, interact with the app, take another, compare. Look for detached DOM trees – nodes removed from the document but still referenced from JavaScript. They cannot be garbage-collected.
Common leak patterns
// listener never removed
function mountPanel(root) {
window.addEventListener('resize', onResize);
// missing: return () => window.removeEventListener('resize', onResize);
}
// closure holding DOM
const cache = [];
function remember(el) {
cache.push(el); // keeps entire subtree alive
}
// forgotten timer or interval
setInterval(pollServer, 1000);
// clearInterval on teardown
Fix pattern: return a cleanup function from init features and call it when the UI is destroyed or the route changes.
export function initDashboard(root) {
const controller = new AbortController();
const { signal } = controller;
window.addEventListener('resize', onResize, { signal });
root.addEventListener('click', onClick, { signal });
return () => controller.abort();
}
AbortController with { signal } removes multiple listeners in one call.
WeakRef and FinalizationRegistry
Advanced tools for caches that should not keep objects alive forever. Use sparingly – most leaks are fixed by disciplined teardown, not weak references.
const cache = new Map();
function cacheView(key, element) {
cache.set(key, new WeakRef(element));
}
function getCachedView(key) {
const ref = cache.get(key);
return ref?.deref() ?? null;
}
Long tasks and INP
Tasks over 50ms block input responsiveness. Break work into chunks with requestIdleCallback, queueMicrotask, or setTimeout(0), or offload to workers. Interaction to Next Paint (INP) is a Core Web Vital – profiling helps you see which handlers lag.
function processInChunks(items, chunkSize, fn) {
let index = 0;
function step() {
const end = Math.min(index + chunkSize, items.length);
for (; index < end; index++) fn(items[index], index);
if (index < items.length) {
requestAnimationFrame(step);
}
}
step();
}
What to measure first
- Time to interactive on your slowest target device.
- Heap size after navigating away and back ten times.
- Long tasks during scroll and typing.
- Network waterfall for blocking scripts and fonts.
The next tutorial introduces Progressive Web Apps – service workers and an offline shell.

