Advanced CPU Profiling in Node - Best Practices and Pitfalls
Table of Contents
Part 1 of 3 in our Advanced CPU Profiling in Node series
- Advanced CPU Profiling in Node - Best Practices and Pitfalls
- Advanced CPU Profiling in Node - Real-Life Examples
- Advanced CPU Profiling in Node - Profile Data Structure

CPU profiling is a powerful but intricate part of optimizing Node.js application performance. While it is easy to get started with the basics, using profiling effectively requires a deeper understanding of the available tools, how they work, and what the results mean. This article introduces advanced CPU profiling techniques using Node.js’s built-in tools. You will learn how to create CPU profiles, troubleshoot common issues, manage and interpret profiling data, and adjust profiling arguments for different scenarios. With practical examples and detailed explanations throughout, you will gain the confidence you need to analyze and improve your application's performance.
How to create a CPU Profile
To create a CPU profile, you can use the --cpu-prof.cpuprofile
Official docs: Node.js CPU Profiling
You can start CPU profiling in different ways
Direct command line flags
Using NODE_OPTIONS (Node.js v23.x+)
☝️ Note
The order of CPU profiling arguments is critical!
❌ Bad:
✅ Good:
Troubleshooting
What, of the many profiles, is the main process?
Based on the file naming conventions and the nature of process ID assignment, the main process always has the smallest Process ID (PID), and a Thread ID (TID) is 0 and appears at the end of an alphabetically sorted folder. The main process is the last file in the folder.
My profile files are appearing in different places in the file system based on the cwd of every process
This is because of the way Node.js handles the --cpu-prof--cpu-prof-dir
Now all of them are in one place:
Error: --cpu-prof is not allowed in NODE_OPTIONS
--cpu-prof is not allowed in NODE_OPTIONSNode.js maintains a security whitelist for NODE_OPTIONS
Version Support
≤ v22.x: All CPU profiling flags blocked in
NODE_OPTIONS≥ v23.x: All CPU profiling flags allowed (PR #57018)
Test your Node.js version support
Workarounds for v22.x and earlier
CPU Profile Filename
Date and Time
The date and time in the filename represent the wall-clock write time, which is when the profile was flushed. This timestamp generally corresponds to the process creation time, not the moment the file was written to the file system.
Process and Thread IDs
The CPU profile filename includes both a Process ID (PID) and a Thread ID (TID). Understanding how these IDs are generated is crucial for interpreting profile files, especially in applications involving multiple processes or worker threads.
A Node.js application starts as a single process. This main process can then spawn child processes or create worker threads. Each new process and worker thread is assigned a distinct ID, which is reflected in the CPU profile filenames. Node.js provides built-in mechanisms for accessing these identifiers. For instance, process.pidworker_threads.threadIdthreadId
The following script demonstrates how to retrieve these values:
Output:
What Determines the Process ID (PID)?
The PID represents the OS-level process ID.
A new PID is generated each time a new process is created. Common ways to create new processes in Node.js include:
child_process.fork()child_process.spawn()child_process.exec()Directly running the
executable (e.g., starting a new script).node
Consider the following script that spawns two child processes:
Output (order of spawn children may vary slightly due to asynchronous nature):
☝️ Note: The process ID
is assigned incrementally. The initial process is always the smallest PID and TID is 0. process.pid
If --cpu-profnode --cpu-prof script.js
(Parent process)CPU.<timestamp>.51430.0.001.cpuprofile (First child process)CPU.<timestamp>.51431.0.002.cpuprofile (Second child process)CPU.<timestamp>.51432.0.003.cpuprofile
What Determines the Thread ID (TID)?
The TID represents V8's internal thread identifier.
By default, Node.js applications run in a single main thread, which typically has a TID of 0Workernode:worker_threads
The following script creates two worker threads:
Output (order of worker messages may vary):
If --cpu-prof
(Main thread)CPU.<timestamp>.51430.0.001.cpuprofile (Worker 1)CPU.<timestamp>.51430.1.002.cpuprofile (Worker 2)CPU.<timestamp>.51430.2.003.cpuprofile
Sequence number
The sequence number (.001.002
CPU Profiling Arguments
☝️ Note: The "Added in" versions indicate when these flags were introduced. Some flags were initially experimental. Always consult the official Node.js documentation for the most current information on their status and behavior in your Node.js version.
Flag | Added in | Default | Description |
| v12.0.0 (Exp) | off | Starts the V8 CPU profiler on startup and writes a .cpuprofile on exit. |
| v12.0.0 (Exp) | Current working directory. If | Directory where |
| v12.0.0 (Exp) |
| Filename to use for the CPU profile. |
| v12.2.0 (Exp) |
| Sampling interval in microseconds for the CPU profiler. |
--cpu-prof
--cpu-profPurpose: Starts the V8 CPU profiler on application startup. When the Node.js process exits, a
file containing the profiling data is written..cpuprofileDefault:
offUsage Example:
This command will profile and generate a profile file (e.g., CPU.<timestamp>.<pid>.<tid>.<sequence>.cpuprofile
--cpu-prof-dir
--cpu-prof-dirPurpose: Specifies the directory where CPU profile files generated by
will be saved.--cpu-profDefault: The current working directory. If
is set, it defaults to that directory instead.--diagnostic-dirUsage Example:
This will save the CPU profile into the ./profiles
Notes:
If
specifies an absolute path,--cpu-prof-name is ignored.--cpu-prof-dirEnsure the specified directory is writable by the Node.js process.
--cpu-prof-name
--cpu-prof-namePurpose: Specifies a custom filename for the generated CPU profile.
Default:
(see CPU Profile Filename for details on the pattern).CPU.<yyyymmdd>.<hhmmss>.<pid>.<tid>.<seq>.cpuprofileUsage Example:
This will generate a profile file named my-custom-profile.cpuprofile--cpu-prof-dir
Notes:
You CANNOT use placeholders for filenames (e.g.,
,${pid} ) in your custom name.${timestamp}If an absolute path is provided (e.g.,
), the file WILL NOT be saved there./tmp/my-custom-profile.cpuprofile
--cpu-prof-interval
--cpu-prof-intervalPurpose: Sets the sampling interval for the CPU profiler in microseconds (µs). This determines how frequently the profiler captures the state of the JavaScript call stack.
Default:
(microseconds/µs), which is 1 millisecond.1000Usage Example:
This command profiles an inline script (console.log('CPU')
How to use intervals?
The --cpu-prof-interval
A smaller interval (e.g., 50 µs, 100 µs) collects more samples, providing a highly detailed profile. This is useful for short-running processes or identifying fine-grained bottlenecks. However, it results in larger
files and can add slightly more overhead to the profiled application..cpuprofileA larger interval (e.g., 1000 µs, 5000 µs) collects fewer samples, resulting in smaller profile files and reduced overhead. This is suitable for longer-running processes or when a high-level overview of CPU usage is sufficient.
Start with the default (1000 µs) and adjust based on the granularity of data you need and the acceptable size of the generated profile files. The following section includes a table that illustrates the impact of different intervals on a sample application.
Illustrative Example: Impact of Interval Choice
This example demonstrates how different --cpu-prof-interval
Step 1: Generate Profiles with Different Intervals
Run the following command to create profiles with intervals ranging from 1μs to 10,000μs:
Step 2: Analyze the Generated Profiles
Use this command to compare the profiles and display their characteristics:
Step 3: Results Analysis
The table below shows how the sampling interval affects profile characteristics:
Profile File | Sampling Interval | Duration (Script) | Total Nodes | Total Samples | File Size | Visual Example |
|---|---|---|---|---|---|---|
| 1 µs | 58.5 ms | 472 | 10,514 | 162.0 KB |
|
| 10 µs | 17.5 ms | 338 | 931 | 73.3 KB |
|
| 50 µs | 16.0 ms | 245 | 312 | 45.2 KB |
|
| 100 µs | 15.0 ms | 153 | 100 | 28.9 KB |
|
| 1000 µs (1 ms) | 13.9 ms | 49 | 10 | 8.6 KB |
|
| 2000 µs (2 ms) | 14.2 ms | 32 | 7 | 5.8 KB |
|
| 4000 µs (4 ms) | 13.5 ms | 18 | 4 | 3.2 KB |
|
| 5000 µs (5 ms) | 14.8 ms | 15 | 3 | 2.7 KB |
|
| 7000 µs (7 ms) | 13.2 ms | 9 | 2 | 1.8 KB |
|
| 10000 µs (10 ms) | 14.0 ms | 3 | 2 | 0.5 KB |
|
Examples of Combining Arguments
1. Experiment Comparison
Organized profiling for A/B testing or feature comparison experiments.
2. Production Monitoring
Low-overhead profiling with reduced sampling frequency.
3. Performance Benchmarking
High-detail profiling with frequent sampling for bottleneck analysis.
4. CI/CD Performance Testing
Automated profiling in build pipelines using NODE_OPTIONS
This works for spawning both child processes and worker threads.
All profiles end up in the same directory due to the absolute path.
The sampling interval is very high to enable long-running processes to be profiled.
5. Handling options with .env
Conclusion
Advanced CPU profiling is essential for keeping Node.js applications running smoothly as they grow. By applying these techniques, you can find and fix performance issues before they escalate into larger problems that affect users. As you gain experience with these tools, you will find that even the most complex optimization challenges become more approachable.
Part 1 of 3 in our Advanced CPU Profiling in Node series
- Advanced CPU Profiling in Node - Best Practices and Pitfalls
- Advanced CPU Profiling in Node - Real-Life Examples
- Advanced CPU Profiling in Node - Profile Data Structure
Read next

Angular v21 Goes Zoneless by Default: What Changes, Why It’s Faster, and How to Upgrade
If Signals were Angular’s “aha!” moment, Zoneless is the “oh wow—this feels snappy” moment. With Angular v21, new apps use zoneless change detection by default. No more zone.js magic under the hood—just explicit, predictable reactivity powered by Signals.

What Is LCP And Why It Matters: Unlock Instant Page Experiences - Part 1
Largest Contentful Paint (LCP) is a Core Web Vital that shapes user perception of speed. This first part explains what LCP is why it matters for SEO and business, and how its phases affect site performance.

What Is LCP And Why It Matters: Unlock Instant Page Experiences - Part 2
Part 2 is a practical walkthrough to diagnose and speed up LCP. Learn to read CrUX trends, profile in Chrome DevTools, preload critical assets, use srcset, defer third-party scripts, and code-split Angular bundles to turn red LCP into green.

Advanced CPU Profiling in Node - Real-Life Examples
Profiling is easiest when it's real. Learn how to capture and make sense of CPU profiles in Node.js across scripts, threads, and processes—then apply it to your own projects.

Advanced CPU Profiling in Node - Profile Data Structure
CPU profiles are more than flame charts—they’re structured JSON files. Learn how nodes, samples, and time deltas form the backbone of DevTools performance data.

Implementing Incremental Hydration in Angular (Part 3/3)
Implement incremental hydration in a real-world Angular app - Basic setup, hydration triggers and important considerations for a seamless integration.












