Difficulty: Beginner
Node.js provides several timer functions for scheduling code execution. These timers are fundamental building blocks for many asynchronous patterns, from delaying operations to implementing polling, debouncing, and retry logic. While they look similar to browser timers, there are important differences in behavior and additional Node-specific features.
setTimeout(callback, delay) schedules a callback to run after at least the specified number of milliseconds. The key word is 'at least' because the actual delay may be longer if the event loop is busy processing other callbacks. setTimeout returns a Timeout object (not a numeric ID like in browsers) that you can pass to clearTimeout to cancel the timer. A delay of 0 does not mean immediate execution; it means the callback will be queued for the next iteration of the event loop's Timer phase, after all synchronous code and microtasks complete.
setInterval(callback, delay) repeatedly executes a callback with a fixed time delay between each call. Like setTimeout, it returns a Timeout object that can be cancelled with clearInterval. Be cautious with setInterval: if the callback takes longer to execute than the interval delay, callbacks will stack up. For long-running periodic tasks, it is safer to use recursive setTimeout, where you schedule the next execution only after the current one completes. This guarantees a consistent delay between the end of one execution and the start of the next.
setImmediate(callback) is a Node.js-specific timer that schedules a callback to execute in the Check phase of the current (or next) event loop iteration. It is conceptually similar to setTimeout(fn, 0) but executes in a different phase. setImmediate is useful when you want to break up a long-running synchronous operation into chunks to let I/O callbacks process in between. It is cancelled with clearImmediate.
queueMicrotask(callback) schedules a microtask that runs before any macrotask timers. It is similar to process.nextTick but uses the Promise microtask queue instead of the nextTick queue. Since microtasks run between event loop phases, queueMicrotask is useful when you need something to execute after the current synchronous code but before any I/O or timer callbacks. Unlike process.nextTick, recursive queueMicrotask calls are interleaved with other microtasks rather than forming an uninterruptible chain.
Modern Node.js also provides promise-based timer APIs through the 'timers/promises' module. These return Promises instead of using callbacks, making them much cleaner to use with async/await. You can write 'await setTimeout(1000)' to pause execution for one second, and use AbortController to cancel timers. This is the preferred approach in modern Node.js code.
// Basic setTimeout
setTimeout(() => {
console.log('Executed after 1 second');
}, 1000);
// setTimeout with 0 delay (still async)
console.log('Before setTimeout');
setTimeout(() => {
console.log('setTimeout 0 - runs after sync code');
}, 0);
console.log('After setTimeout');
// Cancelling a timer
const timer = setTimeout(() => {
console.log('This will NOT print');
}, 5000);
clearTimeout(timer);
console.log('Timer cancelled');
setTimeout(fn, 0) does not execute immediately. It queues the callback for the next Timer phase. clearTimeout cancels a pending timer before it fires. The synchronous console.log statements always run first.
// setInterval: fixed delay between START of each call
let intervalCount = 0;
const interval = setInterval(() => {
intervalCount++;
console.log(`Interval tick ${intervalCount}`);
if (intervalCount >= 3) {
clearInterval(interval);
console.log('Interval cleared');
// Start recursive setTimeout demo
recursiveTimer(1);
}
}, 100);
// Recursive setTimeout: fixed delay between END of each call
function recursiveTimer(count) {
if (count > 3) {
console.log('Recursive timer done');
return;
}
console.log(`Recursive tick ${count}`);
setTimeout(() => recursiveTimer(count + 1), 100);
}
setInterval fires every N ms regardless of how long the callback takes. Recursive setTimeout waits N ms after the callback completes before scheduling the next one. The recursive pattern is safer for tasks with variable execution times.
const { setTimeout: sleep } = require('timers/promises');
async function main() {
console.log('Starting...');
// Await a delay (like sleep in other languages)
await sleep(100);
console.log('After 100ms');
await sleep(100);
console.log('After 200ms total');
// With AbortController for cancellation
const ac = new AbortController();
const { signal } = ac;
// Cancel after 50ms
setTimeout(() => ac.abort(), 50);
try {
await sleep(1000, 'result', { signal });
} catch (err) {
console.log('Timer aborted:', err.code);
}
console.log('Done');
}
main();
The timers/promises module provides async-friendly timer functions. You can await them like sleep functions in other languages. AbortController lets you cancel pending timers, which is useful for implementing timeouts on operations.
setTimeout, setInterval, setImmediate, queueMicrotask, clearTimeout, AbortController