setImmediate() vs nextTick() vs setTimeout(fn,0) – in depth explanation

Few days back, I was guiding some new node.js developers on making asynchronous stuffs. We were discussing about async apis of node js. I wanted to provide them with some references and googled for few; but surprisingly, majority of the articles out there in the internet about setImmediate() or process.nextTick() was containing insufficient or misleading information. And going through official documents of Node may not really be feasible for non-advanced developers. Hence I decided to come up with this article.

Know the misconceptions first

Before I start describing anything, I would like to clear some of the misconceptions of the other articles, covering this topic. If you are not misled yet, you can skip this section.

setImmediate() runs before setTimeout(fn, 0)

This is one of the most common misconceptions. I will discuss about the right concepts later on this article, but below is a proof of this statement being false.

If the statement above was true; running the above code would have given an output where SETIMMEDIATE would have been printed always before SETTIMEOUT. However in reality, the output of the above is not predictable. If you run node index.js multiple times, you will find multiple orders.

setImmediate() puts the callback ahead of the job queue

If the statement above was true; it would have produced the following output.

But the actual output is like the following; irrespective of how many times you run it.

nextTick() triggers the callback on next tick (iteration)

Actually both process.nextTick() and setImmediate() was named wrongly. If we swap the names of those then the names will match the functionality. However as in JavaScript, they do not deprecate/change apis, so the named continued as wrong.
In terms of functionality, process.nextTick() is actually the way to invoke a callback immediately. Callback in setImmediate() will be triggered during/next iteration.

How node.js event loop works

The only way to understand the workflow and the differences between these three functions; you must understand the functioning of the event loop. Hope you already know that event loop handles all async callbacks, but here we will discuss how it does so.

event-loop

Each rectangular box in the diagram represent a phase and event loops iterates on those again and again, starting from timers to close callbacks. There is also a nextTickQueue in the middle, however it’s not a part of the event loop itself. Each phase has a queue attached to it. When event loop enters in a particular phase, its target is to execute the callbacks/tasks in those queues. A little description about the phases are as below.

Timer: It handles the callbacks assigned by setTimeout & setInterval after the given time threshold is completed.
I/O callbacks: Handles all callbacks except the ones set by setTimeout, setInterval & setImmediate. It also does not have any close callbacks.
Idle, prepare: Used internally.
Pole: Retrieve new I/O events. This is which makes node a cool dude.
Check: Here the callbacks of setImmediate() is handled.
Close callbacks: Handles close connection callbacks etc. (eg: socket connection close)
nextTickQueue: Holds the callbacks of process.nextTick(); but not a part of the event loop.

How event loop propagates

It enters the Timer phase & checks if anything (callback) is there in the timer queue. If there are some, it starts executing one after another till either the queue is empty or the maximum allowed callback execution is completed.

After Timer it moves to the I/O callback phase where it again find the queue associated with it for i/o operations. It followed the similar approach as timer and after task done moves to the next phase.

Idle phase is used by node internally; for preparation etc. After that, the event loop enters the Poll phase where it handles events. If there is no event to be handled then also the event loops waits a bit in the poll phase for new i/o events. Nothing in the event loops works when poll phase is in waiting or sleep mode. However if there are some scripts assigned by setImmediate the event loop will end the poll phase and continue to the Check phase to execute those scheduled scripts.

After Check it will try executing anything in Close callbacks and after that goes back to Timer for the next iteration or tick.

Now about nextTickQueue. Any callbacks assigned by process.nextTick() is queued in the nextTickQueue and the event loop executes them one after another another, till the entire queue is drained out; after completing the ongoing operation; irrespective of which phase it is in.

This concludes the event loop description and now we may try to understand the three apis mentioned in the title of this article.

setImmediate()

So first of all, by the workflow of event loop, now we can say setImmediate() is not exactly immediate, but the queue containing the callbacks of this, will be executed once in every iteration (when event loop is in Check phase).

So, the example in previous section; things were non-deterministic, because it depends on the performance of the process. However if we move the piece of code in an I/O callback; we can guarantee that the callback of setImmediate will be called before setTimeout, irrespective of anything else.

setTimeout(fn,0)

This also invokes the callback, but will not be executed till the event loop enters the Timer phase. So any setTimeout(fn, 0) along with setImmediate() in the Close callback phase will guarantee the execution of setTimeout 0 before the setImmediate. And accordingly, keeping the phase diagram of event loop in your mind, you can easily determine whether it’s setTimeout(fn, 0) or setImmediate() which will be called at the earliest.

process.nextTick()

process.nextTick() is actually interesting cause irrespective of the current phase of the event loop, this will start being executed right after the current operation is completed. So if the event loop is in Timer and there were 5 callbacks in the timer queue already; and event loop is busy executing the third one. By that time if few process.nextTick() callbacks are pushed to nextTickQueue, the event loop will execute all of them synchronously after completing the current callback execution (which is 3rd one) and will resume the Timer callback execution again from the 4th callback.

Performance measurement

Well, this section is added to this article after Jason Lee, a viewer posted a question in the comment section below. Thanks Jason, you just helped other users as well.

Question

Why the code snippet below, takes a way longer time to get executed, if the line number 6 is replaced with setTimeout(foo, 0)?

I am using Node 8.4.0 in my mac and it takes 7ms to get executed with line number 6 as setImmediate; however it becomes over 1200ms if I change that line with setTimeout.

So why is it like that? To find the answer, let’s modify the code snippet above to have some more visibility on the execution.

So now, on each execution it will print the iteration number i and also triggers a setTimeout which prints a simple string setTimeout.
Now if we run this, it prints something like below.

Not exactly this output, but something similar; where the printings are in bunch. Few callbacks were pulled from check phase, executed them (which also triggers setTimeouts); then after the maximum allowed execution in check phase it tries to complete another tick and reaches the timer phase; executes the things it found there; then came back to check phase… like that.

Now let’s modify the code snippet again to understand the behavior if we replace the setImmediate(foo) with setTimeout(foo, 0).

Very similar to the previous code, but interchanged setImmediate and setTimeout. And below is the kind of output we get if we run it.

This is because there is actually nothing called setTimeout(fn,0) in practical life. Node internally converts the timer value to 1ms if you provide anything below 1ms (or anything bigger than 2147483647ms).
So when it ran setTimeout(foo, 0), it was actually setTimeout(foo,1) and due to this latency, after the execution of the first foo, it didn’t find anything in the timer queue and moved to the next phase in the event loop (cause only pole phase have waiting capability, no one else). Then it executed the setImmediate callback which was invoked by the foo(). Executes it and went back to timer phase; found the new foo callback there and the process repeats.
So here, it was able to execute only one foo() in each tick. Thus, more iteration i the event loop, which takes more time. Thus slow.

What happens if process.nextTick?

What will happen if we use process.nextTick instead of setTimeout(fn, 0) or setImmediate. Let me tell you, it will run way more faster; cause nextTick queue doesn’t has a maximum allowed callback limit. So it will first finish all the callback executions thrown by nextTick and then move to any other phase. The output here will be like below.

As the output is saying; it first finished all the printing of i; i.e. execution of foo(); cause they were getting invoked in nextTick queue. Then it moved to the other things.

About This Author

Hello! I am Paul Shan, a JavaScript Expert, Full Stack and DevOps Engineer cum Consultant based out of Bengaluru, India.

  • WWII

    This is by far the best illustration of the topic..

    • Ano Nym

      Yeah, I agree!

    • Paul Shan

      Thanks 🙂

  • Павел Ашифин

    Very good explanation!
    Thanks a lot!

  • Christian Andrade

    agree, is the best illustration of the topic

  • One of the best explanations I’ve ever read about the event loop!

  • Jason Lee

    Thanks for clarifying this up! But I still have a question with the code below, you can see that using setImmediate() is significantly faster than setTimeout(, 0), how do you explain this fact?

    var i = 0;
    var start = new Date();
    function foo () {
    i++;

    if (i < 1000) {
    // this is much slower
    // setTimeout(foo, 0);
    setImmediate(foo);
    } else {
    var end = new Date();
    console.log(end – start);
    }
    }

    foo();

  • Ravi I

    In the answer to Jason Lee’s question you wrote this ‘Few callbacks were pulled from check phase, executed them, then after the maximum allowed execution in check phase it tries to complete another tick and reaches the timer phase’. But when foo() executes for the first time only one foo() is added to check phase queue. Then it checks for timer queue but there is nothing because of 1ms delay and then it executes the foo() in check queue which adds only one foo() to the check queue again. This time there might be a one or few logs in timer queue which get executed and then the foo() in check queue executes again which pushes another foo() into check phase and it goes on. so here is my question. What do you mean by the statement I mentioned above like why are there multiple foo()’s in the check queue according to your explanation. Can you please explain more on that and also would you write more about when the event loop gets initiated and from when and which phase does the execution start in different scenarios. Sorry for the long post but the event loop explanation got me confused and didn’t find anything on the web for my question.

    • Paul Shan

      At any given time, there is only 1 foo in the check queue.
      But, unlike setTimeout (minimum 1 ms delay), setImmediate doesn’t have any waiting time.
      Now, suppose, the first foo is getting executed after pulling it from the check queue. While executing it will invoke another foo in the check queue, cause there is no 1ms delay. So right after finishing the execution of the first foo, it will find that there is another foo in the check queue and will execute it.
      Whereas in case of setTimeout, it won’t find the second foo right after finishing the the first, because of the 1ms delay.

    • Ravi I

      Thanks for the clarification. That helped a lot.