# Discover JavaScript Timers

> Learn how setTimeout and setInterval schedule JavaScript tasks, cancel timers, handle timing delays, and avoid overlapping asynchronous work.

Author: [Flavio Copes](https://flaviocopes.com/about/) | Published: 2018-03-31 | Updated: 2026-07-18 | Topics: [JavaScript](https://flaviocopes.com/tags/js/) | Canonical: https://flaviocopes.com/javascript-timers/

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:

```js
setTimeout(() => {
  console.log('Two seconds passed')
}, 2000)
```

`setTimeout()` returns a timer ID. Save it when you might cancel the timer:

```js
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:

```js
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:

```js
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:

```js
setTimeout(() => {
  console.log('timer')
}, 0)

console.log('current task')
```

The result is:

```text
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:

```js
const intervalId = setInterval(() => {
  console.log(new Date().toLocaleTimeString())
}, 1000)
```

Stop it with `clearInterval()`:

```js
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:

```js
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:

```js
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()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout) and [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval), plus the [Node.js timers documentation](https://nodejs.org/api/timers.html).
