Skip to main content

setTimeout and setInterval in JavaScript

setTimeout schedules a callback once after a minimum delay; setInterval calls it repeatedly on every interval tick until you stop it.

Theory

TL;DR

  • setTimeout fires once, like a kitchen timer for one pizza. setInterval fires forever, like an oven alarm that beeps every 5 minutes until you turn it off.
  • Main difference: one-shot vs. repeating.
  • Need to run something once? Use setTimeout. Need a loop? Use setInterval, but chain setTimeout for better control with async work.
  • Both return an ID you can pass to clearTimeout or clearInterval to cancel.
  • The delay is a minimum, not a guarantee. The event loop may add more time.

Quick example

javascript
// setTimeout: fires ONCE after delay const timeoutId = setTimeout(() => console.log("Fired once"), 100); // clearTimeout(timeoutId); // cancel before it fires // setInterval: fires REPEATEDLY every interval const intervalId = setInterval(() => console.log("Tick"), 1000); setTimeout(() => clearInterval(intervalId), 5000); // stop after ~5 ticks

setTimeout adds one task to the queue. setInterval keeps adding a new task every interval, whether the previous one finished or not.

Key difference

setInterval schedules the next call the moment each interval elapses. It does not wait for the callback to finish. If your callback takes 1.5 seconds and the interval is 1 second, calls start queuing up behind each other. setTimeout fires once and stops. To get a repeating timer that actually waits for the callback to complete, chain setTimeout inside itself.

When to use

  • Hide a loading spinner after a delay: setTimeout.
  • Poll an API every 5 seconds: setInterval for simple cases, or chained setTimeout when the callback is async.
  • Debounce a search input: chain setTimeout with clearTimeout on each keystroke.
  • Smooth 60fps animation: requestAnimationFrame, not timers.
  • Avoid blocking the UI: any timer beats a while loop.

Comparison table

FeaturesetTimeoutsetInterval
ExecutionOnce after delayRepeatedly every interval
Minimum delay~4ms (browsers clamp nested calls)~4ms, but calls can overlap
CancellationclearTimeout(id)clearInterval(id)
Async safetyPredictableCan pile up if callback is slow
When to useDebounce, one-off delays, cleanupClocks, simple polling

How the browser handles this

setTimeout and setInterval are not part of the JavaScript engine. They live in the browser's Web API layer (or libuv in Node.js). When the delay elapses, the browser places the callback on the macrotask queue. The event loop picks it up only after the current call stack and all microtasks (Promises) are clear. That is why setTimeout(fn, 0) does not run immediately. Zero milliseconds is not zero time.

Browsers also enforce a minimum ~4ms delay for nested setTimeout calls beyond 5 levels deep (HTML5 spec). In background tabs, browsers throttle timers even more, sometimes up to 1000ms.

Common mistakes

Passing a function call instead of a reference

javascript
function greet(name) { console.log(`Hi ${name}`); } setTimeout(greet("Alice"), 1000); // runs immediately, passes undefined to setTimeout setTimeout(() => greet("Alice"), 1000); // correct

greet("Alice") executes right away and returns undefined. You are passing that result to setTimeout, not the function itself.

Forgetting cleanup in React components

javascript
// Memory leak: interval survives unmount useEffect(() => { setInterval(() => fetchData(), 5000); }, []); // Correct useEffect(() => { const id = setInterval(fetchData, 5000); return () => clearInterval(id); }, []);

The interval keeps running after the component unmounts. The fix is one line: return a cleanup function.

Using setInterval with slow async callbacks

javascript
// Calls can overlap if fetch takes longer than 5s setInterval(async () => { const data = await fetch("/api/status"); updateUI(data); }, 5000); // Better: wait for each call to finish function poll() { fetch("/api/status") .then(res => res.json()) .then(data => { updateUI(data); setTimeout(poll, 5000); }); } poll();

Trying to change the interval from inside the callback

javascript
let delay = 1000; setInterval(() => { delay *= 2; // does nothing - interval was fixed at scheduling }, delay); // If you need dynamic delays, chain setTimeout: let delay = 1000; function tick() { console.log("Tick"); delay *= 2; setTimeout(tick, delay); } setTimeout(tick, delay);

Real-world usage

  • React search input: useEffect + setTimeout for 300ms debounce, clearTimeout in cleanup.
  • Redux DevTools: setInterval for periodic state snapshots.
  • VS Code: chained setTimeout for typing debounce in autocomplete.
  • Express/Node.js: graceful shutdown timers, setTimeout to force-close after a cleanup window.
  • For animations use requestAnimationFrame; for real-time data use WebSockets instead of polling.

Follow-up questions

Q: Why does setTimeout(fn, 0) not run immediately?
A: It pushes the callback to the macrotask queue. The event loop runs all sync code and microtasks first, then picks it up. Zero delay means "as soon as possible after current work", not "right now".

Q: What is the ~4ms clamp?
A: The HTML5 spec throttles nested setTimeout calls (after 5 levels) to a minimum of 4ms. Background tabs can be throttled to 1000ms or more to save CPU.

Q: When should you prefer chained setTimeout over setInterval?
A: Any time the callback is async or takes variable time. Chaining guarantees the next call starts only after the previous one finishes, so calls never overlap.

Q: How do you cancel setInterval safely when the ID is captured in a closure?
A: Store the ID in a variable outside the callback: const id = setInterval(...); setTimeout(() => clearInterval(id), 5000). Never rely on the callback to store its own ID.

Q: In a tab where tasks take 100ms each, what happens to setInterval(..., 10)?
A: Callbacks queue up but do not run in parallel since JS is single-threaded. The event loop processes them one by one, so actual intervals end up around 100ms, not 10ms. You get a backlog, not concurrency.

Examples

Basic: deferred single action

javascript
console.log("Start"); const id = setTimeout(() => console.log("Fired"), 100); console.log("End"); // Output: Start -> End -> Fired // "End" prints before "Fired" because setTimeout is async

setTimeout does not block. The engine moves on to console.log("End") immediately, and fires the callback later from the macrotask queue.

Intermediate: debounced search input in React

javascript
function SearchInput() { const [query, setQuery] = useState(""); useEffect(() => { const id = setTimeout(() => { if (query) fetchResults(query); }, 300); return () => clearTimeout(id); // cancel if user keeps typing }, [query]); return <input onChange={e => setQuery(e.target.value)} />; } // Each keystroke resets the timer. // The API call fires only after 300ms of silence.

Every time query changes, the old timer is cancelled and a new one starts. The request goes out only when the user pauses.

Advanced: chained setTimeout vs setInterval for API polling

javascript
// setInterval version - can overlap if fetch is slow const id = setInterval(async () => { const res = await fetch("/api/health"); const data = await res.json(); console.log(data.status); }, 2000); // Chained setTimeout - waits for each response function pollHealth() { fetch("/api/health") .then(r => r.json()) .then(data => { console.log(data.status); setTimeout(pollHealth, 2000); // next poll after this one completes }) .catch(() => setTimeout(pollHealth, 5000)); // back off on error } pollHealth();

The chained version lets you adjust the delay based on the result and handle errors cleanly. I have seen the setInterval version cause double-requests in production when servers get slow under load. One call was still in flight when the next one fired.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?