Skip to content
FLAVIO COPES
flaviocopes.com
2026

Discover JavaScript Timers

By

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:

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: