Question presented to candidate: "You add a setInterval for a periodic background task, and now your CLI tool refuses to exit even after its real work is done. What is happening, and what is the fix?"
What a strong answer should cover:
- Node's event loop keeps a process alive as long as there is pending work β a scheduled timer counts as pending work by default, which is precisely why a lingering
setIntervalorsetTimeoutprevents a script from exiting even after everything else has finished. - π
Timeout.unref()tells Node not to count this timer when deciding whether to keep the process alive. If nothing else is keeping the event loop alive, the process exits, and this specific timer simply never fires again β it does not get force-run or its callback skipped-but-logged, it just stops mattering to the exit decision. - π
Timeout.ref()reverses that β it puts the timer back into the group of things that do keep the process alive. Callingref()on an already-ref'd timer, orunref()on an already-unref'd one, is a harmless no-op. - The common real use case: a periodic health-check or keep-alive timer that should run while other real work is happening, but should never be the reason a script or CLI tool hangs around after that real work is done.
- π Timer drift:
setIntervalschedules its next firing relative to when the current callback finishes, not against a fixed absolute clock. If a callback takes longer than the interval itself, every subsequent tick is pushed later by that same amount β and that offset does not self-correct; it persists into all following ticks. - The practical consequence:
setIntervalis not a stopwatch or a scheduler with hard real-time guarantees. For anything that genuinely needs to run at a fixed wall-clock cadence regardless of how long each execution takes, the standard fix is to compute the next target time explicitly and schedule a freshsetTimeoutfor exactly that remaining duration, rather than trustingsetInterval's fixed nominal period.
Clarifying questions expected:
- "Is this timer meant to be the ONLY thing keeping the process alive, or does real request-handling already do that?" β decides whether
unref()is even relevant here. - "Does this interval's callback ever do work that could take longer than the interval itself?" β that is exactly the condition that produces drift.
Code / implementation expected: Yes β showing the actual process-exit timing difference with and without unref(), and a real measured drift after a slow callback, is far more convincing than describing either behavior.