JavaScript Code Profiling: Finding Bottlenecks
\n\nWhen your Node.js project starts to slow down, the first thought is "I need to rewrite everything in Rust." But don't rush! Most often, the problem lies in a few lines of code that are working inefficiently. In this article, we'll break down how to find these "bottlenecks" using simple profiling tools. You'll learn how to measure performance, build a flamegraph, and understand where the CPU time is going.
\n\nWhy Profile Code?
\n\nImagine your server is processing 100 requests per second, but you need 500. You could optimize everything, but that would take weeks. Or you could find one function that takes 80% of the time and fix it in an hour. Profiling is exactly what shows you where time is being spent.
\n\nLet's consider the main scenarios when profiling is necessary:
\n- \n
- High API response latency \n
- Memory leaks \n
- High CPU load with no apparent cause \n
- Slow processing of large data arrays \n
Tool 1: Built-in Node.js Profiler (--prof)
\n\nThe simplest way is to run Node.js with the --prof flag. It collects execution statistics and saves them to a file named isolate-*.log.
Let's create a test script that contains a bottleneck:
\n\n// slow.js\nfunction heavyComputation() {\n let sum = 0;\n for (let i = 0; i < 1e7; i++) {\n sum += Math.sqrt(i) * Math.sin(i);\n }\n return sum;\n}\n\nfunction fastFunction() {\n return 42;\n}\n\nconsole.time('total');\nheavyComputation();\nfastFunction();\nconsole.timeEnd('total');\n\n\nRun the profiling:
\nnode --prof slow.js\n\nAfter execution, a file named isolate-0x...-v8.log will appear. To read it, use the node --prof-process utility:
node --prof-process isolate-0x...-v8.log > profile.txt\n\nOpen profile.txt and you'll see a table with time percentages:
[Summary]:\n ticks total nonlib name\n 785 78.5% 85.3% JavaScript\n 215 21.5% 14.7% C++\n ...\n[Bottom up (heavy) profile]:\n ticks parent name\n 500 50.0% LazyCompile: *heavyComputation slow.js:2:17\n 100 10.0% Builtin: MathSqrt\n 50 5.0% Builtin: MathSin\n\n\nWe can see that 50% of the time is spent on the heavyComputation function. Now we know what to optimize first.
Tool 2: Chrome DevTools (Inspector)
\n\nFor a more visual analysis, let's connect Chrome DevTools. Run Node.js with the --inspect flag:
node --inspect slow.js\n\nOpen Chrome and go to chrome://inspect. Click "Open dedicated DevTools for Node". Go to the "Profiler" tab → "Start" → run the script → "Stop".
You will see a flamegraph — a chart where the width of a block shows execution time. The wider the block, the "hotter" the function. Our heavyComputation will be a huge rectangle.
Tip: Use the "Focus selected" filter on a function to see its internal calls. For example, you might notice that Math.sqrt is called 10 million times — perhaps it's worth precomputing the roots.
Tool 3: perf_hooks — Precise Measurements in Code
\n\nSometimes you need to measure a specific section of code without running external tools. For this, use the built-in perf_hooks module.
Example of measuring a function's execution time:
\n\nconst { performance } = require('perf_hooks');\n\nfunction processData(items) {\n let result = [];\n for (let item of items) {\n // Simulating complex processing\n for (let j = 0; j < 1000; j++) {\n result.push(item * j);\n }\n