Representative interview topic

Node.js interview: How do you use samplePerIteration to diagnose event-loop delay?

CodingHard
Offer.cc Editorial TeamPublished Updated

Question

You own a Node.js API with intermittent tail-latency spikes. Use monitorEventLoopDelay to distinguish a blocked event loop from a slow dependency, comparing timer-based sampling with samplePerIteration, idle behavior, overhead, and validation.

Prompt and scope

A Node.js API shows P99 latency spikes during traffic peaks, while database and external-service latency do not rise at the same time. Design a diagnosis that distinguishes a blocked event loop from a slow dependency. The runtime is Node.js 26.5.0; its monitorEventLoopDelay API adds samplePerIteration, which samples once per event-loop iteration while retaining interval-based sampling.

Explain sampling semantics, nanosecond units, histogram lifetime, idle-process behavior, and why an observation metric must not be mistaken for request latency.

What the interviewer evaluates

The interviewer looks for a precise definition of what loop delay measures before choosing a mode. A strong answer explains that the histogram must be enabled, read, and disabled, with a window aligned to request metrics. It also recognizes that changing sampling modes changes the sample distribution, so P99 values from different modes are not directly comparable.

The best answers correlate loop delay with CPU, garbage collection, queueing, downstream latency, and instance load. They propose low-cost steady-state monitoring, short high-resolution diagnostic windows, and a rollback plus control comparison.

Clarifying questions before answering

  • Are we locating local blocking or explaining end-to-end P99 directly?
  • Is this a long-lived process, a serverless instance, or a short-lived CLI?
  • What diagnostic window, sampling resolution, and monitoring overhead are acceptable?
  • Are all instances on Node.js 26.5.0, or are versions mixed?
  • Do we also have CPU, GC, request-queue, dependency-latency, and event-loop-utilization metrics?

A 30-second answer framework

“I would define event-loop delay as time in which loop progress is observed later than expected, not as request latency. Node.js reports histogram values in nanoseconds. I would use interval sampling for steady-state monitoring and evaluate per-iteration sampling only for a short diagnostic window. The modes have different sample-generation mechanisms, so their percentiles need separate baselines. I would start and stop a histogram for a fixed window, record P50, P99, maximum, and sample count, and correlate them with CPU, GC, dependency latency, and request P99. If only loop delay rises, I would investigate synchronous CPU work, system calls, and stack traces.”

Step-by-step deep answer

Define the metric boundary

monitorEventLoopDelay returns a delay histogram in nanoseconds. It describes observed delay in event-loop progress at sampling points; it does not cover a request's full network lifecycle and cannot identify a blocker by itself. To explain user-visible P99, align it with request start, queueing, application work, and downstream time in the same window.

Choose between the sampling modes

The default mode samples on a timer controlled by resolution, which suits low-cost steady-state monitoring. Node.js 26.5.0 adds samplePerIteration: true, which samples once per loop iteration. The documentation also states that this mode does not force extra iterations or keep the loop alive while the process is idle.

Per-iteration sampling is useful for short blocking episodes when the instance has continuous loop activity. It produces a different sample count and distribution, so a P99 from one mode cannot be used as a regression result against a P99 from the other mode.

Manage histogram lifetime

Treat the histogram as window state, not as a permanent global accumulator. Create and enable() it at the start of a diagnostic window, read percentile(99), max, and count at the end, then disable() it and export or discard the snapshot. Metric names should include sampling mode, resolution, Node version, and window boundaries.

js
import { monitorEventLoopDelay } from 'node:perf_hooks';

const histogram = monitorEventLoopDelay({
  resolution: 20,
  samplePerIteration: true,
});

histogram.enable();
setTimeout(() => {
  const snapshot = {
    p99Ns: histogram.percentile(99),
    maxNs: histogram.max,
    samples: histogram.count,
  };
  histogram.disable();
  console.log(snapshot);
}, 10_000);

Convert units at the boundary

Histogram values are nanoseconds. Divide by 1_000_000 for milliseconds while retaining the raw value for exact comparisons. Do not treat the maximum as an SLA; one outlier may come from a pause, process freeze, or measurement boundary. Prefer P99, P999, maximum, sample count, and a time series over a fixed window.

Correlate likely blockers

If loop delay and CPU rise together, inspect synchronous JSON serialization, catastrophic regular-expression work, compression, encryption, and large-array traversal. If GC rises with it, inspect heap growth and allocation rate. If loop delay is high while CPU is low, investigate synchronous system calls, lock waits, or host scheduling. If only dependency latency and request P99 rise, local loop metrics cannot replace dependency tracing.

Handle mixed versions and overhead

Record the runtime version at startup and split metrics by version. Instances older than 26.5.0 do not have samplePerIteration; do not silently treat the option as available. Use a larger resolution for steady-state monitoring and a feature flag for short per-iteration windows during incidents. Continuous high-frequency sampling adds observation cost and makes cross-version comparison harder.

Verify and roll back

Create controlled samples with synchronous CPU blocking, timer blocking, GC pressure, and an idle process. Verify unit conversion, clean enable/disable behavior, no artificial wakeups while idle, and alignment with request P99 during a fault. If sampling cost or noise affects the service, disable the diagnostic flag and return to interval sampling; retain a version- and mode-labelled control sample.

High-quality model answer

“I would treat monitorEventLoopDelay as a local scheduling-health signal, not as request latency. I would use resolution for inexpensive steady-state sampling and enable Node.js 26.5.0 per-iteration sampling only in a short diagnostic window for brief blocking. Every window creates, enables, reads, and disables one histogram, and nanoseconds are converted consistently to milliseconds. I would align P99 with CPU, GC, dependency latency, and request P99; only a simultaneous local rise would move my investigation toward synchronous CPU, system calls, or scheduling. The two modes need separate baselines, and mixed runtime versions must be split.”

Common mistakes

  • Treating loop delay as request latency → downstream and queue time remain invisible → define layers and align with request tracing.
  • Forgetting nanoseconds → reports are off by a factor of a million → convert at the boundary and retain raw values.
  • Comparing P99 across modes → sample-generation mechanisms differ → build mode-specific baselines.
  • Leaving per-iteration sampling on forever → cost and noise persist → sample at low cost normally and intensify briefly during incidents.
  • Ignoring idle behavior → operators misread whether sampling wakes instances → test an idle process explicitly.
  • Aggregating runtime versions → older instances lack the option → tag and aggregate by version.

Follow-up questions and responses

Follow-up 1: Does a higher per-iteration P99 prove a performance regression?

No. The observation points, sample count, and distribution changed. Run both modes under the same load, version, and window, compare each with its own baseline, and compare CPU, throughput, and request P99 when measuring monitoring overhead.

Follow-up 2: Why can loop delay be high while CPU is low?

Synchronous system calls, lock waits, host scheduling, or a process pause can delay progress without high user-space CPU. Combine runtime diagnostics, system metrics, and stacks instead of concluding that no blocking exists.

Follow-up 3: Should a serverless function keep this histogram enabled permanently?

Usually not as a cross-request primary signal: the instance lifetime is short and the window may be truncated. A diagnostic build can sample per invocation or for a short batch while recording cold start, execution time, and downstream traces.

Follow-up 4: How do you prove sampling does not change idle behavior?

Run a process with no timers or requests under each mode. Observe exit behavior, loop-iteration counts, and CPU. Per-iteration mode should not force extra iterations for sampling or keep the loop alive.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool