devnotes.

The Timers Module

Timers control when a piece of code runs relative to now — once, after a delay; repeatedly, at an interval; or as soon as the current I/O phase finishes. They're globally available without a require(), and you've already used one of them in the Event Loop page.

setTimeout() — run something once, after a delay

console.log('Starting...');

setTimeout(() => {
  console.log('This runs after roughly 2 seconds');
}, 2000);

console.log('This runs immediately, before the timeout fires');

clearTimeout() — canceling one before it fires

const timeoutId = setTimeout(() => {
  console.log('You will never see this');
}, 5000);

clearTimeout(timeoutId); // cancels it

Useful for things like a "debounced" search box — resetting a timer every time the user types, so the actual search only fires once they've paused:

let debounceTimer;

function onSearchInput(query) {
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    console.log('Searching for:', query);
  }, 300);
}

setInterval() — run something repeatedly

let count = 0;
const intervalId = setInterval(() => {
  count += 1;
  console.log('Tick', count);
  if (count === 5) clearInterval(intervalId); // stop after 5 ticks
}, 1000);

Without the clearInterval() call, this would keep running forever, every second, until the process itself exits.

setImmediate() — run as soon as the current phase of I/O finishes

console.log('First');

setImmediate(() => {
  console.log('Runs after I/O events in this cycle');
});

console.log('Second');

setImmediate() is conceptually similar to setTimeout(fn, 0), but scheduled to run in a different, specific phase of the event loop — the practical difference rarely matters outside of I/O callback contexts, where setImmediate() reliably runs before any timer scheduled for the same cycle.

A note on setTimeout's delay accuracy

The delay passed to setTimeout is a minimum, not a guarantee — if the event loop is busy running other synchronous code when the timer is due, the callback waits until the loop is free, which means it can genuinely fire later than requested under heavy load. This is a direct consequence of everything covered on the Event Loop page: nothing preempts currently-running synchronous code.

A realistic combined example — a periodic health check with a timeout

function checkServerHealth() {
  const timeoutId = setTimeout(() => {
    console.log('Health check timed out — server may be unresponsive');
  }, 5000);

  performHealthCheck().then(() => {
    clearTimeout(timeoutId); // the check succeeded in time, cancel the timeout warning
    console.log('Server is healthy');
  });
}

setInterval(checkServerHealth, 60000); // run the check every minute

This pattern — a timeout paired with a clearTimeout on success — is one of the most common uses of timers in real applications: giving an async operation a deadline, and reacting only if it's actually exceeded.

The Timers Module | DevNotes