setTimeout()
Runs a function once after a minimum delay and returns an ID you can pass to clearTimeout().
setTimeout(func)
setTimeout(func, delay)
setTimeout(func, delay, param1)
setTimeout(func, delay, param1, param2)
setTimeout(func, delay, param1, param2, /* …, */ paramN)
setTimeout(code)
setTimeout(code, delay)- Added in JavaScript Baseline widely available since July 2015
Parameters
| Parameter | Type | Description |
|---|---|---|
$func | Function | The function to execute once the timer expires. |
$code | string | TrustedScript | An alternative to func: a string of source that is compiled and run when the timer expires. It carries the same injection risk as eval() and is strongly discouraged. |
$delayoptional | number= 0 | Milliseconds the timer should wait before running the callback. Omitted or 0 means "as soon as possible", which is the next turn of the event loop, not immediately. |
$param1, …, paramNoptional | any | Additional values passed through as arguments to func when the timer fires. |
Return value
numberA positive integer timeout ID that uniquely identifies the timer in the current global scope; pass it to clearTimeout() to cancel.
Errors
Throws a SyntaxError if a string code argument cannot be parsed, and a TypeError if the first argument is not a function, string or TrustedScript, or if Trusted Types are enforced by CSP and a string is passed with no default policy.
Changelog
- PHP HTML Living StandardAfter five levels of nesting, the delay is clamped to a minimum of 4 ms, so deeply chained zero-delay timers cannot busy-loop the event loop.
- PHP HTML Living Standarddelay is stored as a signed 32-bit integer, so values above 2,147,483,647 ms (about 24.8 days) overflow and may fire almost immediately.
setTimeout() schedules a callback to run once, no sooner than delay milliseconds from now. The word sooner is the whole story: the delay is a floor, not a promise. The callback is queued as a task and can only run when the call stack is empty, so any long synchronous work, a blocking loop or a busy event loop pushes it out arbitrarily far.
When to reach for it
Use it to defer work past the current task — letting a render happen, breaking a long job into chunks, retrying after a backoff, or building a debounce with clearTimeout(). Wrapping it in a promise gives you an awaitable sleep(), which is the closest JavaScript has to blocking.
What trips people up
Extra arguments after delay are forwarded to the callback, so setTimeout(fn, 100, a, b) beats writing a wrapper. A method passed directly loses its receiver: this becomes globalThis, so use an arrow function or bind(). Zero delay does not mean synchronous — setTimeout(fn, 0) still runs after all currently pending microtasks. Background tabs throttle timers heavily (often to one second or worse), and delay is truncated to a 32-bit integer, so anything past roughly 24.8 days wraps around and fires straight away.
Example
When to use it
- Debouncing a search field so the query only runs after typing stops
- Retrying a failed request after an exponential backoff delay
- Yielding to the event loop between chunks of a long computation
- Building an awaitable sleep(ms) helper for async functions
Frequently asked questions
Why does setTimeout fire later than the delay I asked for?
Because the delay is a minimum wait before the callback is queued, not a scheduled execution time. The callback runs only when the call stack empties and the event loop reaches it, so blocking code, other tasks, nested-timer clamping to 4 ms and background-tab throttling all push it later.
How do I make JavaScript sleep for a few seconds?
Wrap setTimeout in a promise and await it: await new Promise(r => setTimeout(r, 2000)). There is no blocking sleep in JavaScript, and a busy-wait loop freezes the whole thread including rendering and other timers.
How do I pass arguments to a setTimeout callback?
List them after the delay: setTimeout(fn, 100, arg1, arg2) forwards arg1 and arg2 to fn. An arrow function wrapper works too, and is the better choice when you also need to preserve this on a method.
More examples
Passing arguments instead of wrapping
The two calls print in delay order without any closure, because everything after the delay argument is handed to the callback as parameters. The final await is only there so the sandbox waits long enough to capture the output.
function log(level, message) {
console.log(`[${level}] ${message}`);
}
// Extra arguments after the delay are forwarded to the callback.
setTimeout(log, 10, "warn", "disk almost full");
setTimeout(log, 20, "info", "cleanup finished");
await new Promise((r) => setTimeout(r, 60));
console.log("both timers have fired");A zero delay still runs after all microtasks
Output comes out 1, 2, 3, 4. A zero-millisecond timeout is a task, and the entire microtask queue — every resolved promise callback — drains before the event loop picks up the next task.
console.log("1: synchronous");
setTimeout(() => console.log("4: setTimeout 0"), 0);
Promise.resolve().then(() => console.log("3: microtask"));
console.log("2: end of synchronous code");
await new Promise((r) => setTimeout(r, 20));A method loses its receiver
Passing counter.bump directly detaches it from the object, so its this points at the global object and the increment lands nowhere — with no error thrown. Only the arrow wrapper and the bound version actually update the counter, which is why the total is 2.
const counter = {
n: 0,
bump() { this.n += 1; },
};
setTimeout(counter.bump, 0); // `this` is globalThis, not counter
setTimeout(() => counter.bump(), 0); // arrow keeps the receiver
setTimeout(counter.bump.bind(counter), 0);
await new Promise((r) => setTimeout(r, 30));
console.log("counter.n =", counter.n); // 2, not 3
Discussion