6 Common Mistakes with Macrotasks in Node.js
When working with Node.js, understanding how macrotasks work...

When working with Node.js, understanding how macrotasks work is crucial for writing efficient and bug-free code. The event loop, which is at the core of Node.js’ asynchronous behavior, has two primary types of tasks: microtasks (like process.nextTick
and Promise
callbacks) and macrotasks (like setTimeout
, setImmediate
, and I/O operations).
Macrotasks run after the currently executing script and all pending microtasks have completed. However, many developers unknowingly misuse macrotasks, leading to performance bottlenecks, unexpected behavior, or race conditions.
1. Assuming setTimeout(fn, 0)
Executes Immediately
Many developers assume that calling setTimeout(fn, 0)
will execute fn
immediately after the current script. In reality, it only schedules the function to run in the next macrotask queue cycle, meaning microtasks (like resolved Promises) will execute first.
Example of Unexpected Behavior
setTimeout(() => console.log("Macrotask: setTimeout"), 0);
Promise.resolve().then(() => console.log("Microtask: Promise resolved"));
console.log("Main script execution");
Output
Main script execution
Microtask: Promise resolved
Macrotask: setTimeout
Even though setTimeout
was given a 0
delay, the microtask from the Promise
runs first because microtasks always execute before macrotasks in the event loop cycle.
Fix
If you need a task to run before setTimeout
, use process.nextTick
or queueMicrotask
.
2. Misusing setImmediate
in I/O-Intensive Code
setImmediate(fn)
schedules a function to run after I/O callbacks but before setTimeout(fn, 0)
. Many assume it's the fastest way to schedule a macrotask, but it’s only beneficial when working with I/O-heavy operations.
Wrong Assumption
fs.readFile("test.txt", "utf8", () => {
setTimeout(() => console.log("setTimeout"), 0);
setImmediate(() => console.log("setImmediate"));
});
Output
setImmediate
setTimeout
This happens because setImmediate
executes before setTimeout(fn, 0)
when inside an I/O callback.
Fix
If you need a task to execute as soon as I/O is done, setImmediate
is fine. But if precise timing matters, consider restructuring your code instead of relying on event loop mechanics.
3. Blocking the Event Loop with Synchronous Code
Node.js relies on the event loop to process macrotasks asynchronously. However, if you introduce heavy synchronous operations, it can block macrotasks from executing.
Problem
setTimeout(() => console.log("Macrotask executed"), 0);
for (let i = 0; i < 1e9; i++) {} // Simulating heavy computation
Here, the setTimeout
callback won’t execute until the loop completes, delaying all macrotasks.
Fix
Offload intensive tasks to worker threads or use setImmediate
strategically to break up execution chunks.
function heavyTask(count) {
if (count === 0) return;
setImmediate(() => heavyTask(count - 1)); // Avoid blocking
}
setTimeout(() => console.log("Macrotask executed"), 0);
heavyTask(5);
4. Forgetting That setTimeout
Minimum Delay Isn’t Guaranteed
A setTimeout(fn, 10)
does not mean fn
will run exactly after 10 milliseconds. If the event loop is busy, the callback will be delayed.
Misconception
console.time("timeout");
setTimeout(() => console.timeEnd("timeout"), 10);
Actual Output (Not Always 10ms)
timeout: 14.387ms
The delay depends on system load and event loop state.
Fix
For precise scheduling, use setImmediate
for immediate execution after I/O or process.hrtime()
for more accurate timing.
5. Overusing process.nextTick
, Causing Starvation
process.nextTick(fn)
allows a callback to run before any macrotasks in the next tick of the event loop. However, excessive use can starve macrotasks, preventing setTimeout
, setImmediate
, or I/O from executing.
Bad Example
function repeat() {
process.nextTick(repeat); // This will never allow I/O or macrotasks to run
}
repeat();
setTimeout(() => console.log("This will never run"), 0);
This creates an infinite loop of microtasks, preventing setTimeout
from executing.
Fix
Instead of process.nextTick
, use setImmediate
to allow the event loop to process macrotasks:
function repeat() {
setImmediate(repeat); // Allows event loop to process other tasks
}
repeat();
6. Not Understanding Event Loop Phases for Timers and I/O
Developers often assume all asynchronous operations behave the same, but Node.js processes them in different event loop phases.
Timers phase: Executes
setTimeout
andsetInterval
callbacksI/O callbacks phase: Executes I/O callbacks
Check phase: Executes
setImmediate
Close phase: Executes closed handle callbacks
Example
setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout"), 0);
fs.readFile("test.txt", "utf8", () => console.log("I/O operation"));
Possible Output
I/O operation
setImmediate
setTimeout
Since setImmediate
executes in the check phase, it runs before setTimeout
, which is in the timers phase.
Fix
Know when each function executes instead of assuming order.
Conclusion
Misunderstanding macrotasks in Node.js can lead to delayed execution, performance bottlenecks, or unintended behavior. The key takeaways are:
setTimeout(fn, 0)
isn’t immediate; microtasks execute first.setImmediate
is useful for post-I/O execution, but not always faster.Blocking the event loop delays all macrotasks.
setTimeout
delays aren’t guaranteed to be exact.Overusing
process.nextTick
can starve macrotasks.Understanding event loop phases helps predict execution order.
You may also like:
1) 5 Common Mistakes in Backend Optimization
2) 7 Tips for Boosting Your API Performance
3) How to Identify Bottlenecks in Your Backend
4) 8 Tools for Developing Scalable Backend Solutions
5) 5 Key Components of a Scalable Backend System
6) 6 Common Mistakes in Backend Architecture Design
7) 7 Essential Tips for Scalable Backend Architecture
8) Token-Based Authentication: Choosing Between JWT and Paseto for Modern Applications
9) API Rate Limiting and Abuse Prevention Strategies in Node.js for High-Traffic APIs
10) Can You Answer This Senior-Level JavaScript Promise Interview Question?
11) 5 Reasons JWT May Not Be the Best Choice
12) 7 Productivity Hacks I Stole From a Principal Software Engineer
13) 7 Common Mistakes in package.json Configuration
Read more blogs from Here
Share your experiences in the comments, and let’s discuss how to tackle them!