Timers: setTimeout and setInterval

setTimeout runs a function once after a delay, setInterval runs it again and again, and clearTimeout and clearInterval cancel them with the ID each timer hands back.

Apprentice · JavaScript · Updated

After this lesson you can

  • Schedule code to run later with setTimeout and again and again with setInterval
  • Cancel a timer with clearTimeout or clearInterval and the ID the timer returns

Key takeaways

  • setTimeout(callback, delay) runs the callback once, after at least delay milliseconds. 1000 milliseconds is one second.
  • setInterval(callback, delay) runs the callback every delay milliseconds until you stop it.
  • Pass the function itself, with no parentheses after its name. With parentheses, it runs right away instead of later.
  • Each timer hands back an ID. Keep it in a variable, and give it to clearTimeout or clearInterval to cancel that timer.
  • A delay is the shortest wait, not an exact one. If the thread is busy, the callback waits until it's free.

Timers are everywhere once you look for them. A pizza tracker says Ready in 10 minutes and counts down. A notification slides in and disappears after three seconds. A photo slideshow moves on every five seconds, a document saves itself every 30 seconds, and a game gives you 60 seconds to finish a level. Each of those is a few lines of JavaScript with a timer, and in this chapter's exercise you'll build one of your own: a workout interval timer.

JavaScript has two kinds of timer, one that runs once and one that repeats, and a way to cancel each.

setTimeout: run something once, later

A kitchen timer rings once. You set it, walk away, and it calls you back when the time's up. JavaScript's kitchen timer is setTimeout. Its shape is:

setTimeout(functionToRun, delayInMilliseconds);
  • The first argument is the function to run later: a callback.
  • The second is how long to wait, in milliseconds, written ms: thousandths of a second. 1000 ms is one second, so 10 minutes is 10 * 60 * 1000.

Here's order #42 going into Corner Slice Pizza's oven. The example waits 2 seconds instead of 10 minutes, so you don't have to:

console.log('Order #42: large margherita, into the oven');

setTimeout(() => {
  console.log('Order #42 is ready for pickup!');
}, 2000);

console.log('Ticket screen: order #42 is baking');
Order #42: large margherita, into the oven
Ticket screen: order #42 is baking
Order #42 is ready for pickup!

The last line of code prints second, because setTimeout doesn't wait: it sets the timer and lets the next line run. Two seconds later, the callback runs.

Hand over the function, don't call it

The most common timer mistake is one pair of parentheses. Here the callback is a named function, and the code hands setTimeout the function called, announceReady(), instead of the function itself:

function announceReady() {
  console.log('Order #42 is ready!');
}

setTimeout(announceReady(), 2000);
console.log('Next order, please');

The message prints at once, not two seconds later, because announceReady() runs right there to work out what to hand over. It hands back undefined, so setTimeout gets no function at all, and Node stops with an error. Among the lines it prints are these two:

Order #42 is ready!
TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received undefined

A browser doesn't stop; it quietly does nothing two seconds later, which is harder to spot. The fix is to leave the parentheses off, setTimeout(announceReady, 2000), so the timer gets the function and calls it itself.

setInterval: again and again

The ticket screen in the kitchen doesn't ring once: it updates every second, showing how long each order has waited. A timer that repeats is setInterval. It takes the same two arguments, and calls the function every time the delay passes:

let secondsWaiting = 0;

setInterval(() => {
  secondsWaiting += 1;
  console.log(`Order #42 waiting: ${secondsWaiting} s`);
}, 1000);

Run it, and it prints Order #42 waiting: 1 s, then 2 s, then 3 s, one line a second, forever. An interval never stops by itself. Press Ctrl+C in the terminal to stop the program, then read on to stop it from code.

Canceling a timer: clearTimeout and clearInterval

When you hand in a coat at a coat check, you get a numbered ticket, and the ticket is how you get that coat back. Every timer works the same way: setTimeout and setInterval each hand back a timer ID, and you give that ID to clearTimeout or clearInterval to cancel that one timer.

Here the customer cancels order #57, so its reminder must never fire:

const reminderId = setTimeout(() => {
  console.log('Reminder: order #57 is still in the oven!');
}, 5000);

console.log('Order #57 canceled by the customer');
clearTimeout(reminderId);
Order #57 canceled by the customer

The program ends at once, with no reminder: the timer was canceled before its five seconds were up. In a browser the ID is a number, and in Node it's an object; either way, you keep it in a variable and hand it back.

An interval usually stops itself when its job is done. This countdown clears its own interval when the minutes run out (each "minute" is one second here):

let minutesLeft = 3;

const timerId = setInterval(() => {
  console.log(`Order #42: ready in ${minutesLeft} min`);
  minutesLeft -= 1;

  if (minutesLeft === 0) {
    clearInterval(timerId);
    console.log('Order #42 is ready!');
  }
}, 1000);
Order #42: ready in 3 min
Order #42: ready in 2 min
Order #42: ready in 1 min
Order #42 is ready!

The callback can use timerId because it runs a second later, when const timerId = ... has long since finished. minutesLeft === 0 uses ===, read "triple equals" or "strictly equal".

Try it. Make a file named countdown.js with the countdown above and run it with node countdown.js. Then change it to count down from 5, and make it print Order #42 is ready! only once.

A delay means "at least", not "exactly"

If Nina, the cook, is in the middle of plating a big order when the oven timer beeps, she finishes plating first. JavaScript's timers work the same way. The delay is the shortest wait: the callback can't run until the thread is free.

const start = Date.now();

setTimeout(() => {
  const waited = Date.now() - start;
  console.log('The timer asked for 100 ms');
  console.log('It waited 500 ms or more:', waited >= 500);
}, 100);

// The cook is busy plating a big order for half a second.
while (Date.now() - start < 500) {}
console.log('Big order plated');
Big order plated
The timer asked for 100 ms
It waited 500 ms or more: true

The timer was due after 100 ms, but the loop kept the thread busy for 500, so the callback waited its turn. Browsers stretch delays on purpose too: a tab you aren't looking at may run its timers only once a second or less, to save battery. So never use a timer to measure time. To know how long something really took, compare Date.now() before and after, as this code does.

On a page: a ticket screen that counts down

On a web page, a timer usually changes what's on the screen. Here's the ticket screen for one order. The page has a paragraph for it, <p id="ticket">Order #42: ready in <span id="time-left">45</span> seconds</p>, and this script counts the number down:

const ticket = document.querySelector('#ticket');
const timeLeft = document.querySelector('#time-left');
let secondsLeft = 45;

const timerId = setInterval(() => {
  secondsLeft -= 1;
  timeLeft.textContent = secondsLeft;

  if (secondsLeft === 0) {
    clearInterval(timerId);
    ticket.textContent = 'Order #42 is ready!';
  }
}, 1000);

Each second the callback runs, changes one number on the page and finishes, so the page stays free for clicks in between. That's the difference from the frozen kitchen in Why Code Can't Stop and Wait: a timer waits without holding the thread.

Try it. Add that paragraph to a page of your own (or to the frozen-kitchen page from the last lesson), load the script with <script type="module" src="ticket.js"></script>, and open the page with Live Preview. While it counts down, click around: the page answers at once.

Check yourself

2 questions to try on the lesson page.

So what

Two timers and two ways to cancel them cover countdowns, reminders, slideshows, auto-saves and game clocks. Keep each timer's ID, cancel what you no longer need, and remember that a delay means "not before", never "exactly then".

Key terms

Cheat sheets

Related