Profiling in DevTools
Profiling is the discipline of finding out where the time actually goes before changing code. Chrome and Edge ship a powerful profiler in DevTools; Firefox has an excellent one too. The screenshots and shortcuts differ, but the concepts are the same: record a slice of execution, walk the resulting timeline, decide what to fix.
The Performance tab
Open DevTools (F12 / Cmd+Opt+I) and switch to the Performance panel. The basic workflow is:
Press Record.
Do the thing you want to measure (click, scroll, navigate, type).
Press Stop. DevTools renders a timeline of everything that happened.
Look at the Main lane to see what the JavaScript thread was doing frame by frame.
Reading the flame chart
The Main lane shows a flame chart — a stack of bars where height = call depth and width = wall-clock time. Wider bars are where the time went; taller bars are deep call chains.
Yellow blocks — JavaScript execution. Click one to see the function in the Bottom-Up / Call Tree tabs.
Purple blocks — layout. If you see a tall purple bar after every JS bar, you have layout thrashing.
Green blocks — painting and compositing. Mostly the browser's problem.
Red triangles — long tasks (>50ms). The browser flags them because they block input.
Use the Bottom-Up view to see which leaf functions cost the most cumulatively across the whole recording — usually the fastest way to find a hot spot.
Marking your own regions
Manually navigating the flame chart is tedious. Use the User Timing API to drop labelled markers in your code; they show up as a coloured strip in the Performance panel.
performance.mark / measure
performance.mark("hydrate:start");
hydrateApp();
performance.mark("hydrate:end");
performance.measure("hydrate", "hydrate:start", "hydrate:end");
// Later, in any tab:
performance.getEntriesByName("hydrate");
// -> [{ name: "hydrate", duration: 412.3, ... }]In Chrome, named measures appear in the Timings lane of the profile. You can zoom directly to them, no scrolling required.
console.time, console.profile
For one-off measurements without recording a full profile, the console is enough:
console.time("query");
const rows = db.query(sql);
console.timeEnd("query"); // "query: 18.7ms"
console.profile("render"); // starts a CPU profile programmatically
heavyRender();
console.profileEnd("render"); // appears in the Profiler tabconsole.profile is handy when the slowness is triggered by something hard to catch with the record button — a debounced handler, a third-party callback, a worker boot.
Memory snapshots
Open the Memory panel for memory-shaped problems (slow growth, unexplained RAM, suspected leaks).
Heap snapshot — a full dump of every reachable JS object. Best for "what is in memory right now".
Allocation instrumentation on timeline — records every allocation while you interact. Spikes that never come back down are leaks.
Allocation sampling — lower overhead, samples allocations. Use for long sessions.
Take a snapshot in a steady state.
Perform the action you suspect leaks (open and close a panel, navigate to a page and back).
Take another snapshot.
Switch the Comparison view; sort by # Delta. Objects whose count grows by exactly N every cycle are the suspects.
The Retainers view answers "why is this object still alive?" by walking from the object back to the GC roots. It is the most important tool for diagnosing leaks.
Long tasks and the Interaction track
Modern Chrome surfaces "Long Tasks" (>50ms) and INP (Interaction to Next Paint) directly in the Performance panel. Pay attention to:
Total Blocking Time — sum of long-task overflow during the recording. Lower is better.
Interactions in the Interactions lane. Click an interaction to see the exact JS that ran before the next paint.
Layout Shifts — flagged with a red bar above the layout track. A shifting page late in load is usually a CSS or image-sizing bug, not a JS one.
Profiling Node.js
Server code profiles the same way, with two extra options:
# Launch Node with the inspector node --inspect-brk server.js # Then open chrome://inspect and click "inspect" on the target. # Performance and Memory tabs work just like in a tab.
# CPU profile from the CLI node --prof server.js # generates isolate-*.log node --prof-process isolate-*.log > profile.txt
For production Node, prefer always-on samplers like clinic.js, 0x or Datadog continuous profiling; they cost ~1% CPU and catch slow paths the dev environment never sees.
A short profiling checklist
Reproduce the slow case in a profile recording — not just in your head.
Throttle CPU and network so the bottleneck stands out.
Look at the widest yellow bar first; that is where most time is spent.
Drop User Timing marks around suspected regions to see exact durations.
For memory: snapshot, act, snapshot, diff, follow retainers.
Fix the biggest item. Re-record. Repeat.