completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Timers: setTimeout & setInterval

Schedule JavaScript callbacks with setTimeout and setInterval, cancel them safely, compare repeating patterns, and measure why timer delays are minimums rather than promises.

By the end you can
  • 01
    Set and cancel timersUse setTimeout and setInterval to run code later, and clearTimeout and clearInterval to cancel it.
  • 02
    Pick a way to repeatChoose setInterval or a nested setTimeout, based on whether the work might take longer than the wait.
  • 03
    Know why timers run lateExplain why a delay is only the shortest wait: busy code, the 4 ms minimum, hidden tabs, and delays that are too big.

A timer is a reminder, not a pause

JavaScript runs one piece of code from start to end before it does anything else. A timer doesn't stop that code. Instead, you hand a function to the host. The host is the program that runs your JavaScript, such as the browser or Node.js. The host waits. Later, it calls your function.

A function that you hand over to be called later is a callback. You met callbacks in the previous lesson. The host does the waiting. The callback runs only after the code that is running now has finished.

This lesson uses four functions:

  • setTimeout runs a callback once, later.
  • setInterval runs a callback again and again.
  • clearTimeout and clearInterval cancel a timer that you no longer want.

You will also measure real delays in this tab. The numbers change from one computer to another. A slow computer, a hidden tab, battery saving, or a page that is still loading can all make a timer late.

Real-life analogyA timeout is a kitchen timer

You set a kitchen timer and keep cooking. Later, the timer rings once. If you no longer need it, you turn it off before it rings. setTimeout sets the timer. clearTimeout turns it off.

In real life: Set a kitchen timer for five minutes
In JavaScript: setTimeout(callback, 5000)
In real life: Turn the timer off before it rings
In JavaScript: clearTimeout(id)
In real life: The cook is busy when it rings, so the cook finishes first
In JavaScript: The callback waits until JavaScript is free
The main idea

A JavaScript timer asks the host to call a function after at least a delay. It never promises the exact millisecond.

Set and cancel a timer

STEP THROUGH

setTimeout(callback, delay) takes a callback and a delay. The delay is in milliseconds (ms), and 1000 ms is one second. setTimeout sets the timer and gives back an id right away. An id is a value that names this one timer:

  • In a browser, the id is a number.
  • In Node.js, the id is a Timeout object.

Save the id if you might want to cancel the timer. You can also pass more values after the delay. The host passes them to the callback when it runs.

Pass values to the callbackPop out in the code editor (opens in a new tab)JavaScript
setTimeout((name, count) => {
  console.log(name + " has " + count + " reminders");
}, 1000, "Ada", 3);

Line 3 passes "Ada" and 3 after the delay of 1000 ms. One second later, line 1 gets them as name and count. Line 2 prints Ada has 3 reminders.

A repeating timer works the same way: set it, save the id, and cancel it with the id. The callback below counts three ticks and then stops its own interval.

Stop an interval from inside its callbackPop out in the code editor (opens in a new tab)JavaScript
let count = 0;
const id = setInterval(() => {
  count += 1;
  console.log("tick", count);
  if (count === 3) clearInterval(id);
}, 5);

Line 2 saves the id in id before the first tick. Every 5 ms, line 3 adds one to count and line 4 prints it. On the third tick, line 5 passes the same id to clearInterval, so there is no fourth tick.

The next code sets three timers, cancels one, and prints two lines of its own. Step through it and watch when each line prints. A timer with a delay of 0 still runs later, not in the middle of the script.

Step through: three timers, one cancelled
Step 0 of 8Ready
Your turn: follow the blue line

Guess the order first. This player replays the order a real run prints, one step at a time.

Running in
  1. script
Next: line 1
const keep = setTimeout(() => console.log("D: 20ms timeout"), 20);const cancel = setTimeout(() => console.log("never runs"), 10);setTimeout(() => console.log("C: 0ms timeout"), 0);clearTimeout(cancel);console.log("B: end");
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The picture shows the three timers that lines 2 to 4 hand to the browser, and the one that line 5 cancels.

A browser window, the host, keeps three timers: keep, 20 ms; cancel, 10 ms; and a timer with no name, 0 ms. An arrow from clearTimeout(cancel) points at the cancel timer, which never runs.browser (host)keep: 20 mscancel: 10 msno name: 0 msclearTimeout(cancel)never runs
Lines 2 to 4 hand three timers to the host. Line 5 takes back the one saved in cancel, so its callback never runs.

Cancelling matters most when you clean up. Say the user presses Reset, leaves the page, or closes part of the page. Clear every timer that is still waiting. If you don't, an old callback can run later and change a page that has moved on.

Now test the "at least" rule yourself. The lab below sets a 500 ms timer. Then it can run a busy loop: a loop that keeps JavaScript busy for a set time. While the loop runs, nothing else can run, not even a timer that is due.

A time line. A busy loop card runs from 0 to 700 ms. An arrow marks 500 ms, when the timer is due, in the middle of the busy loop. The callback card comes right after the busy loop and runs at about 700 ms.busy loop: 700 mscallback0 ms: click500 ms: the timer is duerunsabout 700 ms
You asked for 500 ms, but a 700 ms busy loop holds JavaScript. The callback runs when the loop ends, at about 700 ms.
Timer lab: is 500 ms exactly 500 ms?
Measure a real 500 ms timerPop out in the code editor (opens in a new tab)JavaScript
const started = performance.now();const id = setTimeout(() => {  const actual = performance.now() - started;  console.log(actual);}, 500); // Optional: block the thread before the callback can run.
Resultmeasured on this device
Not run yetAsked for: 500 msBusy loop: 0 ms
Try it yourself

Run the timer. Then pick a busy loop and run it again. The time is measured with performance.now() in your browser when you click.

This measures a real timer in this tab. A hidden tab, battery saving, or a busy computer can change the result. Reset cancels a timer that is still waiting.

Repeat with an interval or a nested timeout

INTERACTIVE

setInterval(fn, 500) tries to start fn every 500 ms. That works well for a clock, or for a small check that repeats. But say the work sometimes takes longer than 500 ms. The interval doesn't wait for it. The next callback can start while the last one's work is still going.

A nested timeout is a setTimeout whose callback sets the next timer when its work is done. So each wait starts only after the work ends.

Real-life analogyA metronome or a timer you set again

A metronome is a small device that ticks at a steady speed while you practice music. It keeps ticking even if you fall behind. A nested timeout is like a kitchen timer that you set again each time you finish a task. So there is always a full wait after the work.

In real life: A metronome keeps ticking, even if you miss a beat
In JavaScript: setInterval keeps starting new callbacks
In real life: Finish chopping, then set the kitchen timer again
In JavaScript: A nested setTimeout waits until the work is done
In real life: Press Stop on the metronome
In JavaScript: clearInterval(id) or clearTimeout(id)
One repeated save, written two waysPop out in the code editor (opens in a new tab)JavaScript
setInterval(async () => {
  await saveSlowly();
}, 500);

async function loop() {
  await saveSlowly();
  timeoutId = setTimeout(loop, 500);
}

Lines 1 to 3 use setInterval. Every 500 ms, it starts a new call to saveSlowly(), even if the last save hasn't finished. Lines 5 to 8 use a nested timeout. Line 6 waits for the save with await. Only then does line 7 set the next timer, 500 ms away.

Top: setInterval starts save 1, then starts save 2 500 ms later while save 1 is still going, so the saves overlap. Bottom: nested setTimeout runs save 1, waits 500 ms after it ends, then runs save 2, one at a time.setIntervalsave 1save 2a new start every 500 mssaves overlapnested setTimeoutsave 1wait 500 mssave 2one at a time
Say one save takes longer than 500 ms. setInterval starts the next save anyway. The nested setTimeout waits for the save to end, then waits 500 ms.

Try both below. Each tick does 150 ms of pretend work, and the bars show when each tick really starts.

Interval or nested timeout: when does each tick start?
An interval and a nested timeoutPop out in the code editor (opens in a new tab)JavaScript
let intervalId;let intervalTicks = 0;let timeoutId;let timeoutTicks = 0; function doSlowWork() {  const end = performance.now() + 150;  while (performance.now() < end) {}} intervalId = setInterval(() => {  intervalTicks += 1;  console.log("interval", intervalTicks);  doSlowWork();  if (intervalTicks === 2) clearInterval(intervalId);}, 500); function nestedTick() {  timeoutTicks += 1;  console.log("nested", timeoutTicks);  doSlowWork();  if (timeoutTicks < 2) timeoutId = setTimeout(nestedTick, 500);} timeoutId = setTimeout(nestedTick, 500);
Start timesabout 5 seconds

No ticks yet.

Try it yourself

Press Start. It runs for about five seconds. Every tick does 150 ms of pretend work. Stop, Reset, and leaving the page clear every timer.

Measured on this device. The bars show real start times. They are for learning, not for exact speed tests.

So how do you choose?

  • Use setInterval for small, steady ticks, like a clock.
  • Use a nested timeout when each run must finish before the next wait starts.
  • Use a nested timeout when the next delay changes each time.
setTimeout, setInterval, or nested setTimeout?
  • Hide a small pop-up message once, 4 seconds after it appears
  • Update a simple clock display every second
  • Ask a server again only after the previous request finished
  • Save 500 ms after the user stops typing
  • Tick a training metronome until Stop is pressed
  • Retry with a different delay after each failed attempt
Try it yourself
0 of 6 correct

Sort each job by the timer you would pick first.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

The shortest wait a timer allows

REAL MEASUREMENTS

A delay of 0 means "as soon as the browser allows". It doesn't mean "right now". Browsers also have rules about the shortest wait, to protect the page and the computer. When a browser raises a delay that is too small up to a minimum, that is called clamping.

One rule comes from the HTML standard, the rule book that browsers follow. A timer that is set inside another timer's callback is nested. After five levels of nesting, each wait is at least 4 ms. Your measured numbers may be bigger, or they may jump around.

Clamping demo: ten nested 0 ms timers
Measure ten nested 0 ms timersPop out in the code editor (opens in a new tab)JavaScript
let depth = 0;let last = performance.now(); function next() {  const now = performance.now();  console.log(depth, now - last);  last = now;  depth += 1;  if (depth < 10) setTimeout(next, 0);} setTimeout(next, 0);
Measured gaps0/10

No measurements yet.

Try it yourself

Run ten nested 0 ms timers. You will see how long each one really waits.

Measured on this device. A hidden tab or browser settings can change the numbers. Reset cancels the timer that is still waiting.
Very large delays break

Timers keep the delay in a 32-bit number. The largest delay that fits is 2 ** 31 - 1 ms, about 24.8 days. A bigger delay overflows: it doesn't fit, so the timer uses a different delay. In Node.js, setTimeout(fn, 2 ** 31) runs almost at once and shows a TimeoutOverflowWarning. Browsers have the same limit. So don't use one huge timer to wait for weeks.

A negative delay, a missing delay, or NaN ("not a number") doesn't throw an error. The host treats it as a very short delay, like 0. The callback still runs later, and it can still wait behind other code. So use plain, sensible numbers for delays. For a reminder on a calendar date, check the date instead of setting one long timer.

Clearing twice is safe. If you call clearTimeout twice with the same id, nothing breaks and nothing runs again. So a Reset button and a clean-up step can both clear the same timer.

Why the wait is only a minimum

MEASURE

Here is the rule you can trust: a timer waits at least the delay you asked for. It can run later when:

  • the script that is running now is still busy,
  • another callback is running,
  • the tab is hidden and the browser slows its timers down,
  • or the computer is busy with other work.

Browsers often slow the timers in a hidden tab down to about once per second. Some browsers slow them even more.

That is why every demo in this lesson measures time with performance.now(). It gives the number of milliseconds since the page opened. The results you see are measured on this computer, in this tab, when you click. They are not copied from someone else's computer.

A hidden tab may pause its timers or slow them down to save battery. So don't count ticks to know how much time has passed. For a countdown, save the time when it should end. Each time a callback runs, work out end - performance.now() and show that.

Timer callbacks also have a rule about this. Inside a method, this is the object before the dot, like user in user.start(). But the host calls a timer callback on its own, with no object before the dot. So a normal function callback doesn't get your object as this. An arrow function has no this of its own. It uses the this of the code around it.

An arrow callback keeps thisPop out in the code editor (opens in a new tab)JavaScript
const user = {
  name: "Asha",
  start() {
    setTimeout(() => console.log(this.name), 0);
  },
};

user.start();

Line 8 calls user.start(), so inside start, this is user. The arrow callback on line 4 uses that same this. So when it runs, it prints Asha.

In Node.js, the id has extra methods, such as unref(). They belong to Node.js, not to browsers. To keep your code working everywhere, only pass the id to the matching clear function.

Some tools can be stopped with an abort signal. An abort signal is an object that says "stop now" once it is aborted. fetch, which loads data from a server, is one of these tools.

A signal that aborts itself after one secondJavaScript
const controller = new AbortController();
const timeout = AbortSignal.timeout(1000);
console.log(timeout.aborted);
controller.abort();
console.log(controller.signal.aborted);

Line 1 makes an AbortController, an object you can abort by hand. Line 2 makes a signal that aborts itself after 1000 ms. Line 3 prints false, because no time has passed yet. Line 4 aborts the controller by hand, so line 5 prints true.

An abort signal is not a timer id

AbortSignal.timeout(1000) makes a signal that is aborted after 1000 ms. You can give it to fetch, or to another tool that listens for an abort signal. It can't cancel a setTimeout timer. For that, use clearTimeout with the id.

The table puts the timer functions side by side. Read each row as: this function runs this, you cancel it like this, and it gives back this.

The timer functions side by side
FunctionWhat does it run?How do you cancel it?What does it give back?Good to know
setTimeout(fn, delay, ...args)It runs fn once, after at least delay ms.Call clearTimeout(id).A number in browsers. A Timeout object in Node.js.Values after the delay are passed to fn when it runs.
setInterval(fn, delay, ...args)It runs fn again and again, trying to start every delay ms.Call clearInterval(id).A number in browsers. A Timeout object in Node.js.It never stops by itself. You must clear it.
Nested setTimeoutIt runs once, then sets the next timer when its work is done.Save the newest id and call clearTimeout(id).The same as setTimeout.It is a good fit for asking a server again and again without overlap.
setImmediate(fn) (Node.js only)It runs fn soon, after Node.js finishes its current round of input and output work.Call clearImmediate(id).An Immediate object.Browsers don't have it.

Where you’ll use timers

Timers show up all over web pages. You use them to:

  • hide a small pop-up message after a few seconds,
  • wait for the user to stop typing before you search,
  • ask a server for new data again and again,
  • show a countdown,
  • or stop a loading spinner if something takes too long.

Two other lessons go deeper. Closures in loops, timers & memory explains a common trap with loop variables. Losing this explains why setTimeout(obj.method) loses its object.

To poll a server means to ask it for new data again and again. The code below polls with a nested timeout, so two requests never overlap.

Poll a server without overlapPop out in the code editor (opens in a new tab)JavaScript
let timeoutId;
let stopped = false;

async function poll() {
  await loadLatestData();
  if (!stopped) timeoutId = setTimeout(poll, 1000);
}

poll();

function stopPolling() {
  stopped = true;
  clearTimeout(timeoutId);
}

Line 5 waits for the data to load. Line 6 sets the next timer, but only if polling hasn't stopped. stopPolling does two things: line 12 sets stopped to true, so no new timer is set, and line 13 cancels the timer that is waiting. Real apps call it from a Reset button, or when the user leaves the page.

Debounce means waiting for a quiet moment before you act. A search box can use it, so three quick key presses cause one save, not three. A similar idea is throttle: let an action run at most once in each time window. Throttle suits updates while the user scrolls or resizes the window. The small debounce below keeps only the last value.

Save only the last searchPop out in the code editor (opens in a new tab)JavaScript
let timerId;
function saveSearch(text) {
  clearTimeout(timerId);
  timerId = setTimeout(() => console.log("save", text), 10);
}

saveSearch("c");
saveSearch("ca");
saveSearch("cat");

Each call to saveSearch clears the timer before it on line 3, then sets a new one on line 4. The calls on lines 7 and 8 set timers that the next call clears. Only the timer from line 9 is left, so the code prints save cat once.

Three calls: saveSearch("c"), saveSearch("ca"), and saveSearch("cat"). Each one makes a timer. Each call clears the timer before it, so the "c" and "ca" timers are cleared. Only the "cat" timer runs, and it prints save cat.saveSearch("c")saveSearch("ca")saveSearch("cat")timer: "c"timer: "ca"timer: "cat"clearsclearsclearedclearedrunsprints save cat
Each call clears the timer before it, then sets a new one. Only the last timer is left, so only save cat prints.

For work that changes the page while the user scrolls, requestAnimationFrame is often a better fit. It runs your function just before the browser draws the next picture of the page. Timers don't know when the browser draws.

Common mix-ups

  • “0 ms means right now.” It means later: after the script ends, when the browser can run the callback.
  • “500 ms means exactly 500 ms.” It means at least 500 ms. Busy code and hidden tabs can make it later.
  • “An interval waits for my async work.” It doesn't know when your promise finishes. Use a nested timeout, or a flag that skips a tick while the work is still going.
  • “Clearing a timer after it ran undoes it.” Clearing only cancels a timer that is still waiting. Once a callback has run, clearing can't undo it.
  • “A huge delay is a safe long reminder.” Delays above 2 ** 31 - 1 ms overflow, in Node.js and in browsers.
  • “Timer ids look the same everywhere.” Browsers give back numbers. Node.js gives back objects.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict timer order

Read the code and type the four printed words in order.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log("start");
setTimeout(() => console.log("slow"), 20);
setTimeout(() => console.log("fast"), 0);
console.log("end");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeBuild a countdown you can cancel

    Write a countdown that prints 5, 4, 3, 2, 1, 0, one number each second. Use a nested timeout, and write a function that cancels it.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    let timeoutId;
    let remaining = 5;
    
    function tick() {
      // print remaining, then schedule the next tick
    }
    
    function cancelCountdown() {
      // stop the pending tick
    }
      Exercise 3 · PracticeFix slow work that overlaps

      Rewrite the interval as a nested timeout, so a slow request can't overlap the next one.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      setInterval(async () => {
        await loadLatestData();
      }, 1000);
        Exercise 4 · ChallengeDebounce a preview

        Here input is a text box and preview is an element on the page. Change the code so the preview updates only after the user stops typing for 300 ms.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        input.addEventListener("input", () => {
          preview.textContent = input.value;
        });
          Exercise 5 · PracticeCancel on Reset

          Predict what prints. Like a Reset button, the code clears one timer but leaves another one waiting.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const id = setTimeout(() => console.log("save"), 10);
          clearTimeout(id);
          setTimeout(() => console.log("done"), 0);

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Timers quiz · 7 questionsScore: first tries count
            1. Question 1 of 7In setTimeout(fn, 500), what does the 500 mean?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print first, second, and third?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log("A");
              setTimeout(() => console.log("C"), 0);
              console.log("B");

              Choose an answer to see the explanation.

            3. Question 3 of 7Which pairs of set and cancel functions go together?

              Choose an answer to see the explanation.

            4. Question 4 of 7Why can a nested setTimeout be better than setInterval for asking a server again and again?

              Choose an answer to see the explanation.

            5. Question 5 of 7What do the HTML rules say about many nested setTimeout(fn, 0) calls?

              Choose an answer to see the explanation.

            6. Question 6 of 7What usually happens to timers in a browser tab you can't see?

              Choose an answer to see the explanation.

            7. Question 7 of 7In a browser, what does setTimeout give back?

              Choose an answer to see the explanation.

            Key takeaways

            • setTimeout runs a callback once, later. clearTimeout cancels it if it hasn't run yet.
            • setInterval repeats until you call clearInterval. A nested timeout waits the full delay after each run's work ends.
            • A delay is the shortest wait, not an exact time. Measure with performance.now() when the timing matters.
            • Deeply nested timers wait at least 4 ms, hidden tabs slow timers down, and huge delays overflow.
            • Clear your timers on Reset, on Stop, and when the user leaves the page.

            Final definition: a JavaScript timer is a reminder kept by the host. It runs a callback later, after at least the delay, and it never pauses the code that is running now.

            Up next: The event loop.

            CompleteFrontend Clear concepts. Working examples.