How setInterval Works in JavaScript Architecture

setInterval repeatedly calls a function at a fixed time interval. Learn how it interacts with the event loop, why it drifts, and when a recursive setTimeout is the better choice.

7 min read

setInterval is a JavaScript function that calls a given callback repeatedly, with a fixed time delay between each call. Like setTimeout, it is available in browsers and Node.js. Unlike setTimeout, which runs once, setInterval keeps going until you explicitly stop it.

Use setInterval for tasks that need to happen on a regular beat: updating a clock display, polling a server for new data, or running a simple animation loop.

javascriptjavascript
let count = 0;
 
const intervalId = setInterval(() => {
  count += 1;
  console.log(`Tick ${count}`);
 
  if (count >= 3) {
    clearInterval(intervalId);
    console.log("Stopped");
  }
}, 1000);

After running this, the console prints one message per second until the third tick, then stops. The interval keeps calling the function on a fixed beat, and the code inside that function decides when enough ticks have happened to stop it:

texttext
Tick 1
Tick 2
Tick 3
Stopped

setInterval syntax and return value

The call takes a callback and a delay in milliseconds, plus any extra arguments you want forwarded to that callback on every tick. It returns a numeric ID that identifies this specific timer so you can cancel it later.

javascriptjavascript
const intervalId = setInterval(callback, delay, ...args);
ParameterRequiredDescription
callbackYesThe function to run on each tick
delayNoMilliseconds between the start of each call. Defaults to 0
...argsNoExtra arguments forwarded to the callback each tick
ReturnsA numeric interval ID for clearInterval

The delay is measured from the start of one invocation to the start of the next, not from the end of one to the start of the next. This distinction is the root cause of interval drift.

javascriptjavascript
function showTime() {
  console.log(new Date().toLocaleTimeString());
}
 
showTime();
const id = setInterval(showTime, 2000);
 
// Later, when you are done:
// clearInterval(id);

How setInterval interacts with the event loop

Each tick works like a separate setTimeout. When the timer fires, the callback moves to the macrotask queue. The event loop picks it up only when the call stack is empty.

setInterval lifecycle in the event loop

The important detail is the "skip this tick" path. If a callback takes longer than the interval, the browser does not queue extras. It drops the overlapping tick and waits for the next one.

This prevents callback pileups, but it also means you miss executions when your callback is slow.

Interval drift: why setInterval loses time

Because the delay is between starts, any time the callback spends executing pushes the next start later. Over many iterations, the drift adds up. The example below simulates a slow callback with a small helper function first, then uses that helper inside a real interval to measure the drift.

javascriptjavascript
function simulateWork() {
  const spinUntil = performance.now() + 15;
  while (performance.now() < spinUntil) {
    // Busy work
  }
}

This helper just burns about 15ms of CPU time before returning, standing in for any callback that takes noticeable time to finish. Call it from inside a 100ms interval and measure the total time after five ticks:

javascriptjavascript
let iterations = 0;
const startTime = performance.now();
const id = setInterval(() => {
  iterations += 1;
  simulateWork();
  if (iterations >= 5) {
    clearInterval(id);
    const elapsed = performance.now() - startTime;
    console.log(`Expected ${iterations * 100}ms, actual ${elapsed.toFixed(0)}ms`);
  }
}, 100);

Expected output: around 500ms for 5 iterations at 100ms each. Actual output: closer to 575ms because each 15ms callback delays the next tick.

For tasks where consistent timing matters more than a fixed interval, a recursive setTimeout avoids drift by measuring the delay from the end of each call.

javascriptjavascript
function reliableInterval(callback, delay) {
  function tick() {
    const start = performance.now();
    callback();
    const elapsed = performance.now() - start;
    setTimeout(tick, Math.max(0, delay - elapsed));
  }
  setTimeout(tick, delay);
}

This pattern recalculates the remaining delay after each callback, keeping the average interval close to the target. It is covered in detail in JavaScript setTimeout Behavior: Complete Guide.

Clearing an interval with clearInterval

Every setInterval call returns an ID. Pass it to clearInterval to stop the recurring execution.

javascriptjavascript
let seconds = 0;
 
const id = setInterval(() => {
  seconds += 1;
  console.log(`${seconds}s`);
 
  if (seconds >= 5) {
    clearInterval(id);
  }
}, 1000);

Without calling clearInterval, this would run forever and hold a reference to the callback in memory. In a browser, intervals also continue running after the user navigates away unless you clear them in a cleanup step. In Node.js, an active interval also keeps the process alive, so a script that only has a running interval will not exit on its own until it is cleared.

A common pattern: clearing intervals when a DOM element is removed.

javascriptjavascript
function startTimer(displayElement) {
  const id = setInterval(() => {
    displayElement.textContent = new Date().toLocaleTimeString();
  }, 1000);
  return () => clearInterval(id);
}
 
const stop = startTimer(document.getElementById("clock"));
// Later, when the clock element is removed:
// stop();

Returning a cleanup function is a pattern you will see across browser APIs and frameworks. For more on safe timer cleanup, see Clearing Timeouts and Intervals in JavaScript.

setInterval vs recursive setTimeout

setIntervalRecursive setTimeout
Timing baseFixed interval between startsDynamic delay after each end
DriftDrifts if callback takes timeSelf-corrects
Skipped ticksDrops overlapping callbacksNever skips
ControlFixed until clearedDelay can change per iteration
Use caseShort, predictable callbacksVariable work, precise timing

Choose setInterval for short, predictable tasks like updating a live timestamp. Choose recursive setTimeout when the callback duration varies, you need precise timing, or you want to adjust the delay between iterations.

Common setInterval mistakes

Not clearing the interval. An interval that is never cleared runs forever and holds references to its callback and closure variables. In a single-page app, this leaks memory on every route change.

Assuming the callback never overlaps. A 50ms interval with a callback that takes 70ms will skip ticks. The effective interval becomes 70ms or longer.

Using setInterval for exact timing. Neither setInterval nor setTimeout is precise enough for music timing, animation syncing, or benchmarking. For animation, use requestAnimationFrame. For precise timing, measure elapsed time with performance.now() and adjust.

Starting a new interval without clearing the old one. If you call setInterval twice without clearing the first, both run concurrently and their IDs are different. Always call clearInterval before starting a new interval on the same variable.

javascriptjavascript
let timerId;
 
function restartInterval() {
  clearInterval(timerId); // Kill the old one first
 
  timerId = setInterval(() => {
    console.log("Tick");
  }, 1000);
}

Calling clearInterval on an ID that was never set, such as the first time restartInterval runs, does nothing and throws no error, so this pattern is safe to reuse each time the function is called.

Rune AI

Rune AI

Key Insights

  • setInterval schedules a callback to run repeatedly at a fixed delay between starts.
  • The callback will not re-queue if the previous invocation is still running.
  • Drift happens because the delay is measured from the start of each call, not the end.
  • Recursive setTimeout is more reliable when callbacks have variable execution time.
  • Always call clearInterval to stop a running interval and prevent memory leaks.
RunePowered by Rune AI

Frequently Asked Questions

What happens if a setInterval callback takes longer than the interval?

The browser will not queue multiple callbacks for the same interval. If a callback is still running when the next tick fires, that tick is skipped. This is called interval clamping and it prevents callback pileups.

Is setInterval more accurate than setTimeout?

No. Both timers are subject to the same event loop delays. setInterval also has drift: if a callback takes 5ms and the interval is 100ms, the next call happens 105ms later, not 100ms.

Can I change the interval after setInterval starts?

No. The interval is fixed when you call setInterval. To change the timing, clear the current interval with clearInterval and start a new one.

Conclusion

setInterval is the right tool when you need a function to run on a regular beat and the callback is short enough to finish before the next tick. When callbacks are unpredictable in duration or you need dynamic timing, a recursive setTimeout gives you more control and avoids drift.