Node JS discovering slow async calls
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Finding slow asynchronous calls in Node.js is mostly an observability problem, not a syntax problem. You need to measure how long each async boundary takes, correlate that timing with request context, and separate slow external dependencies from event-loop blocking in your own code.
Start with Simple Timing
The fastest first step is to time suspected operations directly.
This is simple and effective for narrowing the search, especially when you already suspect one database call, HTTP request, or file-system operation.
Use perf_hooks for Better Instrumentation
Node’s perf_hooks module provides more structured timing than ad hoc Date.now() calls.
This is a good pattern for app-level probes.
Distinguish Slow Async from Event-Loop Blocking
A slow async call and a blocked event loop are different issues.
- slow async call means the app is waiting on an external operation or deferred work
- event-loop blocking means your JavaScript is monopolizing the main thread
If requests feel slow, check event-loop delay too. Node exposes tools for that through monitorEventLoopDelay.
If event-loop delay is high, the problem may not be the async call itself.
Trace External Dependencies
Many slow async paths come from:
- databases
- HTTP APIs
- Redis or message brokers
- file or cloud storage
Instrument those boundaries explicitly. A wrapper around outbound operations is often enough:
Then use it consistently:
This produces comparable measurements across the codebase.
Use Request Correlation
Timing data is much more useful when it is tied to one request or job. Without correlation, logs from concurrent requests become hard to interpret.
A common pattern is to attach a request ID and include it in every timing log. For larger systems, distributed tracing tools such as OpenTelemetry or vendor APM products are the right long-term answer.
Those tools let you see a whole async chain instead of isolated timings.
Async Hooks and Profilers
When the problem is not obvious, use deeper tooling:
- '
async_hooksfor tracking async resource lifecycles' - the Node inspector and CPU profiles for blocking work
- APM products for distributed tracing and span timing
- database slow-query logs for downstream confirmation
Do not jump straight to async_hooks unless simpler timing failed. It is powerful, but it is also more complex and noisier than targeted instrumentation.
Common Pitfalls
The biggest mistake is timing only the outer request and not the individual async dependencies inside it. That tells you the request is slow but not why.
Another issue is blaming async code when the real culprit is CPU-bound JavaScript blocking the event loop.
Teams also often log timings without correlation IDs, making the data hard to use under concurrency.
Finally, do not ignore the downstream systems themselves. A Node app may be healthy while the real bottleneck is the database, network, or third-party API.
Summary
- Measure suspected async boundaries directly before reaching for heavy tooling.
- Use
perf_hooksfor reliable duration measurements. - Check event-loop delay so you do not confuse blocking code with slow I/O.
- Wrap external dependencies with consistent timing and request correlation.
- Move to
async_hooks, tracing, or APM only when basic instrumentation is not enough.

