W3docs

JavaScript setTimeout and setInterval

Learn how to schedule code in JavaScript with setTimeout and setInterval: syntax, arguments, canceling timers, recursive patterns, and debouncing/throttling.

Sometimes you don't want to run code right now — you want to run it later, or run it repeatedly. JavaScript's two scheduling functions, setTimeout() and setInterval(), let you do exactly that. This guide covers their syntax, how to pass arguments, how to cancel a scheduled timer, the important "recursive setTimeout" pattern, the surprising behavior of a zero delay, and two real-world uses: debouncing and throttling.

Neither function is part of the core JavaScript language — they are provided by the host environment (browsers and Node.js). The behavior described here is the same in both, with a few differences noted along the way.

Introduction to JavaScript Timing Functions

A timer schedules a callback to run after the current code finishes and a given amount of time has passed. Because JavaScript is single-threaded, the callback never interrupts running code; it waits in a queue and runs only when the call stack is empty. (For the full mechanism, see The Event Loop.)

The setTimeout() Function

setTimeout() runs a function once after a specified delay. It accepts a function to execute and a delay in milliseconds before that execution.

Syntax

let timerId = setTimeout(func, delay, arg1, arg2, ...);
  • func — the function (or, less commonly, a string of code) to run.
  • delay — the wait in milliseconds before running. Defaults to 0.
  • arg1, arg2, ... — optional arguments passed straight to func.

The return value is a numeric timer id you can later pass to clearTimeout().

Example

javascript— editable

Passing arguments to the callback

Anything you put after the delay is forwarded to the callback. This is cleaner than wrapping the call in another arrow function:

javascript— editable
Warning
Pass the function itself, not its result. setTimeout(greet(), 1000) runs greet() immediately and schedules its return value (likely undefined). Write setTimeout(greet, 1000) — no parentheses.

The setInterval() Function

setInterval() has the same signature as setTimeout(), but instead of running the callback once, it runs it repeatedly every delay milliseconds until you stop it.

Syntax

let timerId = setInterval(func, delay, arg1, arg2, ...);

Example

javascript— editable
Info
If the callback takes longer to run than the interval, calls can pile up and the real spacing drifts. The browser also guarantees a minimum gap between callbacks, so back-to-back executions can be closer to the next tick than you expect. When precise spacing matters, prefer the recursive setTimeout pattern below.

Recursive setTimeout vs. setInterval

You can reproduce setInterval() by having a setTimeout() callback re-schedule itself. The key difference: setInterval() measures the delay between starts, while recursive setTimeout() measures it between the end of one run and the start of the next — guaranteeing a fixed pause even when the callback is slow.

javascript— editable

Canceling Scheduled Execution

Both functions return a timer id. Hold on to that id and you can cancel the scheduled work with clearTimeout() or clearInterval(). (The two clear functions are actually interchangeable in most engines, but matching them to the scheduler that created the id keeps the code readable.)

Stopping setTimeout()

To cancel a timeout, store the id returned by setTimeout() and pass it to clearTimeout() before the delay elapses.

Example

javascript— editable

Stopping setInterval()

Similarly, save the id from setInterval() and pass it to clearInterval(). Without this, the interval runs forever (or until the page closes), which is a common source of memory leaks and runaway timers.

Example

javascript— editable

The zero-delay setTimeout

setTimeout(func, 0) does not run func immediately. It schedules func to run as soon as the current synchronous code has finished. This is a handy way to "yield" — to let the browser repaint or to break a long task into pieces — and it explains output ordering that often surprises beginners:

javascript— editable

Note that timers are macrotasks: they run after all queued microtasks (such as resolved promise callbacks). See Event loop: microtasks and macrotasks for the precise ordering rules.

Practical Applications and Tips

Two of the most common real-world uses of setTimeout() are debouncing and throttling — both are ways to limit how often a function runs in response to rapid-fire events.

Debouncing with setTimeout()

Debouncing waits until a burst of events has stopped before running the function. Each new event resets the timer, so the callback fires only after things go quiet for wait milliseconds. This is ideal for a search box: you want to send one request after the user stops typing, not one per keystroke.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounced Input Example</title>
<script>
    // Debounce function to limit the rate at which a function is executed
    function debounce(func, wait) {
        let timeout;

        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };

            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Function to be debounced
    function fetchData(input) {
        alert(`API call with input: ${input}`); // Placeholder for an API call
    }

    // Create a debounced version of fetchData
    const debouncedFetchData = debounce(fetchData, 300);

    // Add the debounced function to an event listener
    function setup() {
        document.getElementById('searchInput').addEventListener('input', (event) => {
            debouncedFetchData(event.target.value);
        });
    }

    // Ensure setup is called once the document is fully loaded
    document.addEventListener('DOMContentLoaded', setup);
</script>
</head>
<body>
    <h3>Type in the input field:</h3>
    <input type="text" id="searchInput" placeholder="Start typing..." />
</body>
</html>

Throttling with setTimeout()

Throttling runs the function at most once per limit milliseconds, no matter how many events arrive in between. Where debouncing waits for silence, throttling guarantees a steady cadence — perfect for scroll, resize, or mousemove handlers that would otherwise fire dozens of times a second. The example below uses a leading-edge approach (it runs immediately on the first event, then enforces the gap):

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Throttled Scroll Event</title>
<style>
  /* Simple styling for demonstration */
  body, html {
    height: 200%; /* Make the page scrollable */
    margin: 0;
    padding: 0;
    font-family: Arial, sans-serif;
  }
  #log {
    position: fixed;
    top: 0;
    left: 0;
    background: white;
    border: 1px solid #ccc;
    padding: 10px;
    width: 300px;
  }
</style>
</head>
<body>
<div id="log">Scroll to see the effect...</div>
<script>
// Throttle function using setTimeout
function throttle(func, limit) {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, Math.max(0, limit - (Date.now() - lastRan)));
    }
  }
}

// Function to be throttled
function handleScroll() {
  const log = document.getElementById('log');
  log.textContent = `Scroll event triggered at: ${new Date().toLocaleTimeString()}`;
}

// Add event listener for scroll
window.addEventListener('scroll', throttle(handleScroll, 1000));
</script>
</body>
</html>

Gotchas to remember

  • Delays are a minimum, not a guarantee. If the call stack is busy or the event loop is congested, the callback waits. The number you pass is the earliest it can run, not a promise.
  • Background tabs are throttled. Most browsers clamp timers in inactive tabs to roughly once per second to save power, so animations and polling slow down when the tab is hidden.
  • Nested timeouts are clamped to ~4ms. After five nested setTimeout() calls, browsers enforce a minimum delay of about 4 milliseconds, so a 0 delay is never truly zero in deep chains.
  • Maximum delay. A delay larger than 2147483647 (2^31 − 1) overflows the 32-bit field and is treated as 0, firing almost immediately instead of far in the future.
  • this binding. When you pass a method like setTimeout(obj.method, 1000), it loses its this. Use an arrow function — setTimeout(() => obj.method(), 1000) — or obj.method.bind(obj).
  • Always clean up. Clear intervals (and pending timeouts) when a component unmounts or the work is no longer needed, or you'll leak timers and may operate on stale state.

Conclusion

setTimeout() runs code once after a delay; setInterval() runs it on a repeating schedule; clearTimeout() and clearInterval() cancel them. Remember that delays are minimums, pass arguments after the delay rather than inside a wrapper, reach for the recursive setTimeout pattern when you need steady spacing, and always clean up timers you no longer need. With debouncing and throttling on top, these two small functions cover most time-based work you'll do in the browser.

Practice

Practice
Which of the following statements are true regarding the use of `setTimeout()` and `setInterval()` in JavaScript?
Which of the following statements are true regarding the use of `setTimeout()` and `setInterval()` in JavaScript?
Was this page helpful?