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.
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.
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:
Tick 1
Tick 2
Tick 3
StoppedsetInterval 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.
const intervalId = setInterval(callback, delay, ...args);| Parameter | Required | Description |
|---|---|---|
| callback | Yes | The function to run on each tick |
| delay | No | Milliseconds between the start of each call. Defaults to 0 |
| ...args | No | Extra arguments forwarded to the callback each tick |
| Returns | A 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.
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.
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.
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:
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.
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.
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.
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
| setInterval | Recursive setTimeout | |
|---|---|---|
| Timing base | Fixed interval between starts | Dynamic delay after each end |
| Drift | Drifts if callback takes time | Self-corrects |
| Skipped ticks | Drops overlapping callbacks | Never skips |
| Control | Fixed until cleared | Delay can change per iteration |
| Use case | Short, predictable callbacks | Variable 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.
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
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.
Frequently Asked Questions
What happens if a setInterval callback takes longer than the interval?
Is setInterval more accurate than setTimeout?
Can I change the interval after setInterval starts?
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.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.