πŸš€Debugging Microservices & Distributed Systems
6 min read
advanced

setImmediate() vs setTimeout() in JavaScript

both setImmediate() and setTimeout() are used for scheduling tasks, but they work differently.

JavaScript is known for its non-blocking, asynchronous behavior, especially in environments like Node.js. If you’ve ever worked on a project involving timers or callbacks, you’ve likely encountered setTimeout() and maybe even setImmediate(). At first glance, these two functions might seem like they do the same thing β€” scheduling tasks to run later. But if you’ve ever run them together, you’ve probably noticed some interesting behavior.

Despite their similar purpose, setImmediate() and setTimeout() operate differently under the hood. If you’re wondering why both setImmediate() callbacks seem to run one after the other while setTimeout() callbacks are spaced out, this guide is here to break it down.

This isn’t just a quirk of JavaScript; it’s deeply tied to how Node.js manages asynchronous tasks. Understanding the differences between these two functions will help you better control the timing and execution order of your code, which is especially important for large-scale applications where even a slight misstep in timing can cause hard-to-find bugs.

We’ll take a deep dive into the event loop, how it processes these timers, and why things don’t always happen the way you expect when using them together. By the end, you’ll have a clearer understanding of when to use setTimeout() or setImmediate() based on the timing behavior you need.

Difference in behavior

setImmediate(() => {
console.log("setImmediate 1");
});
setTimeout(() => {
console.log("setTimeout 1");
}, 0);
setTimeout(() => {
console.log("setTimeout 2");
}, 0);
setImmediate(() => {
console.log("setImmediate 2");
});

When you run this, you might expect the setTimeout callbacks to execute in the order they were defined, followed by the setImmediate ones. But what you see in the console is this:

Terminal window
setTimeout 1
setImmediate 1
setImmediate 2
setTimeout 2

If this doesn’t make sense right away, don’t worry. Let’s unravel why it happens this way.

The Event Loop

To understand this, we need to take a quick look at how Node.js manages asynchronous operations. At the core of Node.js’s asynchronous nature is the event loop.

In Node.js, the event loop handles different phases, each responsible for executing certain types of callbacks. It helps manage non-blocking tasks, ensuring that functions can be executed asynchronously. Within these phases, there are different queues. For this discussion, two queues are important:

  • Macrotask Queue: This is where tasks like setTimeout and setImmediate go.
  • Microtask Queue: This is where promises (Promise.then()) and process.nextTick() callbacks go.

The Event Loop

To understand how setTimeout() and setImmediate() work, we need to take a look at the event loop in Node.js. The event loop is what allows Node.js to handle asynchronous code. It processes different types of operations in phases, with each phase responsible for specific tasks.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”Œβ”€>β”‚ timers β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ pending callbacks β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚ idle, prepare β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ incoming: β”‚
β”‚ β”‚ poll β”‚<────── connections, β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ data, etc. β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”‚ check β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
└─── close callbacks β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. Timers Phase: This is where setTimeout() callbacks are handled. Even with a 0ms delay, they wait until the next loop iteration to execute.

  2. Pending Callbacks Phase: Processes completed I/O events, but our example doesn’t have any, so it skips this phase.

  3. Check Phase: setImmediate() callbacks run here. They execute immediately after I/O tasks, but before setTimeout() callbacks.

  4. Poll Phase: Handles new incoming I/O operations like file reads or network requests. If there’s no I/O, the event loop skips this phase.

  5. Next Loop Iteration: After the check phase, the event loop cycles back to handle the next timers phase, where setTimeout() callbacks finally run.

setTimeout() with 0 Delay

When you use setTimeout() with a delay of0, you’re essentially telling Node.js to run the callback as soon as possible after the current operation completes. However, it’s important to remember that β€œas soon as possible” is still dependent on the event loop’s phases.

setTimeout(() => {
console.log("setTimeout 1 with 0 delay");
}, 0);
setImmediate(() => {
console.log("setImmediate 1");
});
setTimeout(() => {
console.log("setTimeout 2 with 0 delay");
}, 0);
Terminal window
setTimeout 1 with 0 delay
setImmediate 1
setTimeout 2 with 0 delay

Even with a delay of 0, the setTimeout() callback still has to wait for the next cycle in the timers phase, so it doesn’t run immediately. Instead, it’s placed in the macrotask queue to be executed in the next available opportunity.

setImmediate()

setImmediate() on the other hand, is designed to execute callbacks after I/O events have completed, in the same event loop iteration. This means setImmediate() callbacks get processed before additional timers like setTimeout() are executed, especially when there’s no I/O involved.

In our example, since there’s no I/O happening, both setImmediate() callbacks are executed back-to-back, before the second setTimeout() callback gets its turn.

Why Do setImmediate Callbacks Run Together?

  • Same Event Loop Tick: Both setImmediate calls are placed into the macrotask queue in the same tick (or cycle) of the event loop. Node.js processes these in order as it loops through the tasks.

  • Priority Over setTimeout(): Even though setTimeout() is scheduled with a 0 delay, that doesn’t guarantee immediate execution. The setImmediate() callbacks have priority over setTimeout() tasks in the current tick.

Real-World Analogy

Think of this like ordering food and drinks at a restaurant.

  1. You order a dish (representing setTimeout(0)).
  2. The chef adds it to the order queue and will deliver it once ready.
  3. Meanwhile, you ask for a glass of water (setImmediate()), and since it’s quick and easy to prepare, the waiter brings it to you right away before your food is done.

In this analogy, the glass of water (quick task) gets handled first, even though both orders were placed around the same time. The dish (a bit more involved) comes out after.

Does This Always Happen?

No. The behavior of setImmediate() and setTimeout() can depend on other asynchronous operations happening in your code. If there’s an I/O operation, the order of execution might change, because setImmediate() will only run after the I/O events are completed.

const fs = require('fs');
fs.readFile('example.txt', () => {
setTimeout(() => {
console.log("setTimeout after I/O");
}, 0);
setImmediate(() => {
console.log("setImmediate after I/O");
});
});
Terminal window
setImmediate after I/O
setTimeout after I/O

In this case, the setImmediate() will always run before the setTimeout() because the event loop prioritizes setImmediate() right after the I/O callback.

When there are no I/O events, both setImmediate() callbacks will run back-to-back, before the setTimeout() callbacks.

process.nextTick() and Promises

Here’s an example showing how various asynchronous operations are handled in Node.js:

setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
Promise.resolve().then(() => {
console.log('Promise then');
});
process.nextTick(() => {
console.log('process.nextTick');
});
Terminal window
process.nextTick
Promise then
setTimeout
setImmediate
  • process.nextTick(): This will run before any other task, even before microtasks like Promises.
  • Promise.then(): This is a microtask, so it runs after the current operation but before macrotasks like setTimeout() and setImmediate().
  • setTimeout(): Runs after microtasks have been processed.
  • setImmediate(): Even though it’s similar to setTimeout(), it runs later in the event loop cycle, after the current I/O operations.

Node.js’ asynchronous behavior can sometimes be confusing, especially when dealing with setTimeout() and setImmediate(). The key takeaway is understanding the event loop and how tasks are scheduled in different phases.

  • setImmediate() runs after I/O events and in the current event loop tick.
  • setTimeout() runs after a specified delay, even if that delay is 0, and it schedules tasks for the next event loop iteration.
  • When there are no I/O operations, setImmediate() will execute back-to-back before the next setTimeout().

Understanding these differences helps you control exactly when your code runs, which is vital in high-performance applications where timing and efficiency matter.


Related Articles

If you enjoyed this article, you might find these related pieces interesting as well.

Recommended Engineering Resources

Here are engineering resources I've personally vetted and use. They focus on skills you'll actually need to build and scale real projects - the kind of experience that gets you hired or promoted.

Imagine where you would be in two years if you actually took the time to learn every day. A little effort consistently adds up, shaping your skills, opening doors, and building the career you envision. Start now, and future you will thank you.


This article was originally published on https://www.trevorlasn.com/blog/setimmediate-vs-settimeout-in-javascript. It was written by a human and polished using grammar tools for clarity.

Interested in a partnership? Shoot me an email at hi [at] trevorlasn.com with all relevant information.