Discover JavaScript Timers
By Flavio Copes
Learn how setTimeout and setInterval schedule JavaScript tasks, cancel timers, handle timing delays, and avoid overlapping asynchronous work.
JavaScript timers schedule a function to run later.
Use setTimeout() to run it once. Use setInterval() to request repeated runs.
The delay is a minimum, not an exact appointment. A callback can run later when the main thread is busy or the browser throttles the page.
Run code later with setTimeout
Pass a function and a delay in milliseconds:
setTimeout(() => {
console.log('Two seconds passed')
}, 2000)
setTimeout() returns a timer ID. Save it when you might cancel the timer:
const timerId = setTimeout(() => {
console.log('Saved')
}, 2000)
clearTimeout(timerId)
Calling clearTimeout() after the callback has already run has no effect.
You can pass arguments after the delay:
function greet(name) {
console.log(`Hello ${name}`)
}
setTimeout(greet, 1000, 'Flavio')
I usually prefer a small arrow function when it makes the call easier to read:
setTimeout(() => greet('Flavio'), 1000)
Always pass a function. Passing a string makes the browser evaluate code and creates the same security problems as eval().
What a zero delay means
A delay of 0 does not run the callback immediately:
setTimeout(() => {
console.log('timer')
}, 0)
console.log('current task')
The result is:
current task
timer
The current task must finish first. Promise callbacks and other microtasks also run before the browser takes the timer task from its queue.
A zero-delay timer can move work to a later task. It cannot make a long calculation cheap. Break CPU-heavy work into small pieces or move it to a Web Worker.
Browsers also clamp deeply nested timers to a minimum delay. Do not depend on repeated zero-delay timers for precise scheduling.
Repeat code with setInterval
setInterval() requests another callback after each interval:
const intervalId = setInterval(() => {
console.log(new Date().toLocaleTimeString())
}, 1000)
Stop it with clearInterval():
clearInterval(intervalId)
The interval starts measuring from when it was scheduled. It does not wait for an asynchronous operation inside the callback to finish.
This can start several requests before earlier ones have completed:
setInterval(async () => {
await fetch('/api/status')
}, 1000)
JavaScript callbacks do not execute at the same instant on one main thread. The problem is that the asynchronous requests can remain in flight together and finish out of order.
Wait for work before scheduling again
For polling, call setTimeout() after the current operation finishes:
let stopped = false
async function poll() {
try {
const response = await fetch('/api/status')
const status = await response.json()
console.log(status)
} finally {
if (!stopped) {
setTimeout(poll, 1000)
}
}
}
poll()
Now the next one-second delay starts after the request settles.
Set stopped = true when the page or component no longer needs to poll. In application code, also use an AbortController to cancel an in-flight fetch.
Timers are not precise clocks
Timers can run late because:
- another JavaScript task is still running
- the browser is rendering or doing other work
- the tab is in the background
- the device is saving power
- nested timers are being clamped
Measure elapsed time with performance.now() or Date.now() when accuracy matters. Do not count timer callbacks and assume each represents an exact interval.
Use requestAnimationFrame() for visual animation. It follows the browser’s rendering schedule and pauses appropriately when the page is not visible.
Browser and Node.js timers
Browsers and Node.js both provide timers, but their event loops and return values differ. Browser timers normally return numeric IDs. Node.js returns timer objects and also provides APIs such as setImmediate().
Do not treat setImmediate() as an exact alias for setTimeout(fn, 0). Their ordering depends on the Node.js event-loop phase and the surrounding code.
See the MDN references for setTimeout() and setInterval(), plus the Node.js timers documentation.
Related posts about js: