What's with setInterval?

Posted on November 17, 2018  -  👩🏼‍💻 5 min read

It all started in Chrome Dev Summit 2018 at the talk Paul & Surma gave about the Actor Model and how it can be applied in web development. Somewhere in the middle of the talk Paul blurbs “I hate setInterval, don’t use it, just use setTimeout instead. If you want to know why come see me after the talk” At that point I couldn’t listen to the talk anymore and was just thinking “WHY…?!”

Just kidding, the talk is really interesting and you should check it out!

After the talk

Well, I took Paul on his word, I reached out to him after the talk because I was still hung up on “WHY…?!” so, I ask him “You’ve probably been asked that a lot by now, but why do you hate setInterval?“. He replied with “Actually you’re the first one. Now think of it this way, your current task might be too long that it’ll queue your callback more than once” It was so obvious now! well, actually not, it took me a couple of more sentences to actually get that, but I got there eventually 🎉

The hate 😠

so let’s try and understand together why this small little nuance was bothering him so much. What does it mean “your current task is too long”? We all should know by now to keep all of our work under 16ms, that way the browser can do all the render pipeline (style, layout, paint, composite) to keep the frame rate at 60fps so our app will be responsive. Unfortunately, that’s not always the case. Sometimes we go overboard, it’s not always bad, if you’re not animating, doing the work in 100ms, 150ms is fine, the user won’t notice anything Let’s say your work takes 150ms, which is still within the reasonable range, and at the start of that work you have

setInterval(() => {
  ...
}, 50);

what’s so wrong with that you ask? apart from not saving the interval id to later cancel it… Not sure? that’s good, it means this hasn’t fully sunk in for you and I get to tell you! As we all know by now, JavaScript is single threaded (I know I know, the scheduler and task proposals that were announced the same day, let’s ignore them as they’re still proposals), and by being single threaded it means that no matter what you do you can’t make the browser stop the current thread, do some other work and come back like in other languages. In JavaScript, when you write

const a = 1;
const b = 2;
...
const c = a + b;

No matter what (unless an error is thrown) there’s no code running between the first two statements and the last one other than the .... This doesn’t mean that the browser doesn’t know how long the entire thing took, oh it knows alright.

If you’ll call setInterval with, let’s say 50ms, and afterwards your current task will continue to run for 150ms, the browser knows, but it can’t stop. What does it do in this situation? add more calls! You see, it doesn’t necessarily have to run your code at exactly every 50ms, it’ll run it at the time closest to 50ms and 100ms and 150ms and 200ms from now. Where now is the time setInterval was called. Let’s go back then, what is the closet time to 50ms from now the browser can run the code? after 150ms when the current task finishes. OK, and what is the closest time to 100ms from now the browser can run the code? after 150ms when the current task finishes and after the next iteration of setInterval. Same thing for when we ask about 150ms from now. Well, the browser just called our callback 3 times almost immediately after we finished the current task, just because it wants to be a good guy and make sure you get the calls you asked for! This’ll exacerbated even further if each of your setInterval callbacks takes more than 50ms which means they’ll just pile up and you’ll end up playing catch-up trying to figure out what went so horribly wrong.

The alternative

Paul also said in the talk to use setTimeout instead, let’s see why. The implementation is pretty known and simple, we just register another setTimeout callback within our original setTimeout callback. Something like this

const fakeSetIntervalCall = () => {
  setTimeout(() => {
    // do some work
    ...
    fakeSetIntervalCall();
  }, 50);
};

Now, there’s a loop of setTimeout calls that happens in intervals of around 50ms. You may ask yourself, “WHY… is that any different?!” Because, for setTimeout calls the browser asks just once when is the closest time to 50ms from now I can run this code? after 150ms when the current task finishes. And that’s it. That’s all the callbacks the browser queues, because it doesn’t execute the inner code just yet to queue the second time. You just ensured your callbacks are executed in a distance of at least 50ms even if it took you more than that time to just execute a single task.

The ❤️

This post was inspired by a super lightning fast chat I had with Paul that made me go “ah, that’s interesting” while he just casually explains it in a couple of sentences and says “I got to get my food now, nice meeting you!” so thank you Paul for taking the time to talk to me about this and I’m sorry I made you wait a little while longer for you to get your food!