setTimeout()
Schedules a callback to run once after a minimum delay in milliseconds.
setTimeout(callback[, delay[, ...args]])- Added in Node.js v0.0.1
Parameters
| Parameter | Type | Description |
|---|---|---|
$callback | <Function> | The function to call when the timer elapses. |
$delayoptional | <number>= 1 | The number of milliseconds to wait before calling the callback. |
$...argsoptional | <any> | Optional arguments to pass when the callback is called. |
Return value
<Timeout>A Timeout object that can be passed to clearTimeout() to cancel the scheduled callback.
Errors
Throws ERR_INVALID_ARG_TYPE if callback is not a function.
Changelog
- PHP v18.0.0Passing an invalid callback to the callback argument now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
setTimeout() puts a callback on the event loop's timers phase and returns a Timeout handle. The delay is a minimum, not a schedule: the callback runs on the first timers phase at or after the deadline, so a blocked event loop or a long synchronous callback elsewhere pushes it later. Node makes no guarantee about exact timing or about the ordering of two timers with the same delay.
When to reach for it
- Retry backoff, debouncing, and any "do this later unless something cancels it" guard.
- Deadlines around work that might hang — pair it with
clearTimeout()in afinallyblock.
What trips people up
The extra ...args exist so you do not have to allocate a closure: setTimeout(fn, 100, id) calls fn(id). In Node the return value is an object, not a number as in browsers — logging it prints a Timeout, and storing it in a Map key or sending it over IPC will not behave like an id unless you coerce it with Symbol.toPrimitive.
The delay is clamped to a signed 32-bit range. Anything above 2147483647, below 1, or NaN silently becomes 1 — which is why a timer set for 25 days fires immediately instead. Non-integer delays are truncated.
A pending timer keeps the process alive; call unref() if it should not.
Example
When to use it
- Retry a failed request after a backoff delay
- Debounce rapid input by resetting a pending timer
- Fail an operation that has not completed within a deadline
- Defer a non-urgent write until after the current burst of work
Frequently asked questions
Why does my setTimeout fire later than the delay I asked for?
Because the delay is a minimum, not a guarantee: the callback runs on the first timers phase at or after the deadline, so synchronous work or a busy event loop delays it. Node explicitly makes no promise about exact timing or about the order of two timers with equal delays.
Why does setTimeout return an object in Node instead of a number?
Node returns a Timeout object rather than the browser's numeric id, so it can expose ref(), unref(), refresh() and hasRef() on the handle. Pass that object straight to clearTimeout(), or call timeout[Symbol.toPrimitive]() if you genuinely need a number.
How do I stop Node from exiting before my setTimeout runs, or stop it from waiting?
A pending timer keeps the event loop alive by default, so the process will not exit until it fires. Call timeout.unref() when the timer is only a safety net and should not delay a clean shutdown.
More examples
A promise-based sleep helper
The output shows two failed attempts with doubling backoff (100ms then 200ms) before 'ok on call 3'. Wrapping setTimeout in a promise is the standard sleep; node:timers/promises gives you the same thing without the wrapper.
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function retry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
const backoff = 100 * 2 ** i;
console.log(`attempt ${i + 1} failed, waiting ${backoff}ms`);
await sleep(backoff);
}
}
}
let calls = 0;
const result = await retry(async () => {
calls++;
if (calls < 3) throw new Error('flaky');
return 'ok on call ' + calls;
});
console.log(result);A deadline that always cleans itself up
Prints 'timed out after 100ms'. The finally clause is what makes the helper safe to reuse: when the wrapped promise wins the race, clearTimeout kills the deadline timer that would otherwise sit pending for the rest of ms and hold the event loop open. It cannot cancel the other side — the 5-second timer inside slow is not reachable from withTimeout, so this particular snippet still idles for the remaining ~5 seconds after printing 'process can exit now'. Cancel the underlying operation itself, with an AbortSignal or req.destroy(), when a prompt exit matters.
function withTimeout(promise, ms) {
let timer;
const deadline = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error(`timed out after ${ms}ms`));
}, ms);
});
return Promise.race([promise, deadline]).finally(() => {
clearTimeout(timer);
});
}
const slow = new Promise((resolve) => setTimeout(resolve, 5000, 'too late'));
try {
await withTimeout(slow, 100);
} catch (err) {
console.log(err.message);
}
console.log('process can exit now');Reusing one Timeout instead of allocating new ones
Shows timeout.refresh() (added in v10.2.0) restarting the countdown on an existing Timeout. On a hot path this avoids the clearTimeout/setTimeout pair and the object allocation that comes with it — this is how Node's own socket idle timeouts work.
const IDLE_MS = 30_000;
const idle = setTimeout(() => {
console.log('connection idle, closing');
}, IDLE_MS);
function onActivity(bytes) {
// Reschedules the same Timeout object from now.
idle.refresh();
console.log('activity:', bytes, 'bytes; idle timer restarted');
}
onActivity(512);
onActivity(1024);
idle.unref();
console.log('idle guard armed but will not block exit');
Discussion