setTimeout, setInterval and their clear functions
The delay is the earliest the callback may run, never the exact moment. Everything awkward about these functions follows from that, plus the fact that setInterval does not wait for your work to finish.
Demo
Timers fire in order of their delay, and when two delays are equal they fire in the order they were registered — so the tie and the zero case both give A first. Now try delays only one millisecond apart, such as 5 and 4: run it a few times and the order can flip. That is the page's central point made visible — the delay is a minimum, not a schedule, and timers that close together are not reliably ordered.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| callback | Function | yes | Run after the delay. A STRING is accepted and evaluated, which is an eval in disguise and should never be used. |
| delay | number | no (0) | Milliseconds to wait at minimum. 0 means "as soon as the current task finishes", not immediately. Nested timeouts are clamped to 4ms after five levels. |
| ...args | any | no (none) | Extra arguments passed to the callback — cleaner than wrapping it in another closure. |
Return value
number | object — A timer id — a number in browsers, a Timeout object in Node. Pass it to clearTimeout or clearInterval to cancel.
Common patterns
const id = setTimeout(fn, 1000); return () => clearTimeout(id);
const sleep = ms => new Promise(r => setTimeout(r, ms)); await sleep(500);
async function loop() { await work(); setTimeout(loop, 1000); }
Examples
Pitfalls
setInterval(async () => { await slowFetch(); }, 1000)
async function loop() { await slowFetch(); setTimeout(loop, 1000); } loop();
setTimeout(() => count++, 1000)
const started = Date.now(); setTimeout(() => { const real = Date.now() - started; }, 1000);
setTimeout(this.tick, 100)
setTimeout(() => this.tick(), 100)
useEffect(() => { setInterval(poll, 1000); }, [])
useEffect(() => { const id = setInterval(poll, 1000); return () => clearInterval(id); }, [])
When to use
- Deferring work until after the current task
- Debouncing and throttling
- Timeouts, paired with Promise.race
- Polling, via a chained setTimeout rather than setInterval
- Animation → requestAnimationFrame
- Running right after the current task → queueMicrotask
- Measuring time → performance.now()
- Regular async polling → chain timeouts, not setInterval
Notes
FAQ
Because it queues a task for after the current one completes, and all pending microtasks run before any task. So a promise callback queued later still runs first. Zero means "soon", not "now".
setTimeout(() => console.log('t'), 0); Promise.resolve().then(() => console.log('m')); // m, then t