Episode - setImmediate in JavaScript — The Complete Guide
Episode - setImmediate in JavaScript — The Complete Guide
Hey everyone! Welcome back to Namaste JavaScript. Today we are going to study a topic that is very confusing for beginners but super important for Node.js interviews — setImmediate!
Most developers know setTimeout and setInterval. But setImmediate is the one that catches everyone off guard in interviews. Once you understand it, you will understand the Node.js Event Loop at a DEEP level!
What we will cover:
- What is setImmediate?
- setImmediate vs setTimeout(fn, 0) — The Big Question!
- setImmediate vs process.nextTick
- The Node.js Event Loop Phases — Deep Dive
- Real-World Use Cases
- clearImmediate
- Interview Questions
What is setImmediate?
setImmediate() is a Node.js function that schedules a callback to execute after the current event loop phase completes — specifically in the check phase of the Node.js Event Loop.
Important: setImmediate is a Node.js API, NOT a browser API. It does NOT exist in the browser (or is non-standard there). It was designed specifically for Node.js to allow deferred code execution in a controlled, predictable phase of the event loop.
Basic Syntax:
==============
setImmediate(callback);
setImmediate(callback, arg1, arg2, ...); // Pass arguments
// Example
setImmediate(function() {
console.log("I run in the CHECK phase!");
});
// With arguments
setImmediate(function(name) {
console.log("Hello,", name);
}, "Shreyesh");
OUTPUT:
I run in the CHECK phase!
Hello, Shreyesh
The Node.js Event Loop Phases — This is the KEY!
To understand setImmediate, you MUST understand the Node.js Event Loop phases. It is NOT just one queue — it has 6 phases!
Node.js Event Loop — 6 Phases:
================================
┌───────────────────────────┐
│ timers │ ← setTimeout, setInterval callbacks
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ pending callbacks │ ← I/O errors from previous iteration
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ idle, prepare │ ← internal use only
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ poll │ ← Wait for I/O (fs, net, http)
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ check │ ← setImmediate callbacks run HERE!
└─────────────┬─────────────┘
│
┌─────────────▼─────────────┐
│ close callbacks │ ← socket.on("close", ...)
└─────────────┬─────────────┘
│
(next iteration)
Between EACH phase:
→ process.nextTick queue drains (all nextTick callbacks)
→ Promise microtask queue drains (all .then callbacks)
Phase Summary:
===============
timers → callbacks from setTimeout() and setInterval()
(runs if timer has expired)
pending → I/O callbacks deferred from the previous iteration
callbacks (e.g., TCP errors)
idle, prepare → internal only, you never interact with this
poll → retrieve new I/O events
if no timers: wait here for I/O to complete
if timers ready: move to timers phase
check → setImmediate() callbacks run here
ALWAYS after the poll phase
close → close event callbacks (socket.destroy, etc.)
callbacks
Between phases: nextTick queue + Promise microtask queue drain FIRST
setImmediate vs setTimeout(fn, 0) — The Most Asked Interview Question!
// What is the output?
setTimeout(function() {
console.log("setTimeout");
}, 0);
setImmediate(function() {
console.log("setImmediate");
});
OUTPUT: (non-deterministic from the main module!)
Could be:
setTimeout
setImmediate
OR:
setImmediate
setTimeout
// WAIT — the order is NOT guaranteed when called from the main module!
// WHY?
Why is the order non-deterministic in main module? ==================================================== When Node.js starts: 1. It enters the EVENT LOOP 2. timers phase runs first — checks if setTimeout(0) has expired 3. setTimeout(0) has a MINIMUM delay of 1ms (not exactly 0ms) If Node.js starts the event loop BEFORE 1ms has passed: → timers phase: no expired timers → SKIP → poll phase: nothing to wait for → check phase: setImmediate fires! → prints "setImmediate" → NEXT iteration → timers: setTimeout fires → prints "setTimeout" If Node.js starts the event loop AFTER 1ms has passed: → timers phase: setTimeout HAS expired → fires! → prints "setTimeout" → poll phase: nothing → check phase: setImmediate → prints "setImmediate" The 1ms timer expiry depends on CPU load and OS scheduling. That's why the order is NON-DETERMINISTIC from the main module!
// ✅ BUT inside an I/O callback — order IS deterministic!
const fs = require("fs");
fs.readFile("./file.txt", function() {
// We are NOW inside an I/O callback (poll phase)
// After poll phase → check phase runs BEFORE next timers phase!
setTimeout(function() {
console.log("setTimeout");
}, 0);
setImmediate(function() {
console.log("setImmediate");
});
});
OUTPUT: (always this order inside I/O callback)
setImmediate ← ALWAYS first
setTimeout ← ALWAYS second
WHY?
After an I/O callback (poll phase):
→ check phase runs NEXT (setImmediate is in check phase)
→ THEN next iteration → timers phase (setTimeout)
setImmediate ALWAYS wins inside an I/O callback!
This is the GUARANTEED behavior.
setImmediate vs process.nextTick — Another Classic!
// What is the output?
setImmediate(function() {
console.log("setImmediate");
});
process.nextTick(function() {
console.log("nextTick");
});
Promise.resolve().then(function() {
console.log("Promise");
});
console.log("Synchronous");
OUTPUT: (always deterministic!)
Synchronous
nextTick
Promise
setImmediate
WHY this ORDER?
================
1. Synchronous code runs first (Call Stack)
→ "Synchronous" prints
2. process.nextTick queue drains (BEFORE any event loop phase)
→ "nextTick" prints
3. Promise microtask queue drains
→ "Promise" prints
4. Event Loop enters check phase → setImmediate fires
→ "setImmediate" prints
The Priority Order in Node.js:
================================
HIGHEST PRIORITY
│
▼
1. Synchronous code (Call Stack)
2. process.nextTick callbacks (nextTick queue)
3. Promise.then callbacks (microtask queue)
4. setImmediate callbacks (check phase — event loop)
5. setTimeout/setInterval callbacks (timers phase — event loop)
│
▼
LOWEST PRIORITY
KEY RULE:
nextTick and Promises drain BETWEEN every event loop phase.
setImmediate runs in the check phase (one specific place).
setTimeout runs in the timers phase (another specific place).
// DEEP EXAMPLE — Nested nextTick and setImmediate
setImmediate(() => {
console.log("setImmediate 1");
process.nextTick(() => console.log("nextTick inside setImmediate"));
setImmediate(() => console.log("setImmediate 2"));
});
process.nextTick(() => console.log("nextTick 1"));
process.nextTick(() => console.log("nextTick 2"));
OUTPUT:
nextTick 1
nextTick 2
setImmediate 1
nextTick inside setImmediate ← drains before next phase!
setImmediate 2
WHY:
- nextTick 1 and 2 drain before event loop starts
- check phase runs: setImmediate 1 fires
- nextTick inside setImmediate is added to nextTick queue
- Before next event loop phase: nextTick queue drains → prints
- check phase continues: setImmediate 2 fires
Real-World Use Cases of setImmediate
Use Case 1: Breaking Up CPU-Intensive Work
// ❌ WITHOUT setImmediate — blocks the event loop!
function processMillionItems(items) {
for (let i = 0; i < items.length; i++) {
// Heavy computation — blocks everything!
doExpensiveWork(items[i]);
}
console.log("Done!");
}
// ✅ WITH setImmediate — yields control between chunks!
function processInChunks(items, index = 0) {
const CHUNK_SIZE = 1000;
const end = Math.min(index + CHUNK_SIZE, items.length);
for (let i = index; i < end; i++) {
doExpensiveWork(items[i]);
}
if (end < items.length) {
// Yield to event loop — handle other events between chunks!
setImmediate(() => processInChunks(items, end));
} else {
console.log("All done!");
}
}
processInChunks(millionItems);
// After processing each chunk of 1000, setImmediate lets the
// event loop handle other events (HTTP requests, etc.)
// before processing the next chunk.
// This keeps your server RESPONSIVE even during heavy work!
Use Case 2: Emitting Events After Constructor
// ❌ Problem: emitting event before listener is registered
const EventEmitter = require("events");
class MyEmitter extends EventEmitter {
constructor() {
super();
this.emit("ready"); // Fires IMMEDIATELY — no listener yet!
}
}
const emitter = new MyEmitter();
emitter.on("ready", () => console.log("Ready!")); // Too late!
OUTPUT: (nothing! event fired before listener was added)
// ✅ FIX: use setImmediate to defer emit
class MyEmitter extends EventEmitter {
constructor() {
super();
setImmediate(() => this.emit("ready"));
// Deferred to check phase — listener is registered by then!
}
}
const emitter = new MyEmitter();
emitter.on("ready", () => console.log("Ready!"));
OUTPUT:
Ready! ← listener catches it now!
// Same pattern used inside Node.js core (fs, net, http modules)!
Use Case 3: Preventing Stack Overflows in Recursion
// ❌ Deep synchronous recursion can overflow the stack
function processNode(node) {
doWork(node);
if (node.child) {
processNode(node.child); // Stack grows with each call!
}
}
// ✅ setImmediate breaks recursion across event loop iterations
function processNode(node) {
doWork(node);
if (node.child) {
setImmediate(() => processNode(node.child));
// Each level runs in a FRESH event loop iteration
// Stack stays shallow — no overflow!
}
}
clearImmediate — Cancelling a Scheduled Callback
// setImmediate returns an "Immediate" object you can cancel
const immediate = setImmediate(() => {
console.log("This will NOT run!");
});
clearImmediate(immediate); // Cancels it before it runs!
console.log("Cleared the immediate!");
OUTPUT:
Cleared the immediate!
// The setImmediate callback never runs — it was cancelled!
// Real-World Use:
// Cancel a deferred operation if a condition changes
// before the check phase arrives.
function scheduleCleanup(resource) {
const immediate = setImmediate(() => {
resource.cleanup();
});
// If resource is released early, cancel the cleanup
resource.on("early-release", () => {
clearImmediate(immediate);
});
}
Full Comparison — setImmediate vs setTimeout vs nextTick vs Promise
Feature nextTick Promise.then setImmediate setTimeout(0)
─────────────────────────────────────────────────────────────────────────────────
Queue nextTick queue microtask check phase timers phase
When runs Before any LP Before any LP After poll, After min 1ms,
phase phase in check phase in timers phase
Priority Highest 2nd 3rd 4th (lowest)
Environment Node.js only Node + Browser Node.js only Node + Browser
Can starve loop? YES (if loops) YES (if loops) No No
Use case Defer but ASAP Promise chain After I/O, Minimum delay
break up work timer
LP = event loop phase
Interview Questions
Q1: What is setImmediate and where does it run in the Node.js Event Loop?
"setImmediate() schedules a callback to execute in the check phase of the Node.js Event Loop — which comes right after the poll phase. The poll phase waits for I/O events; after it completes, the check phase runs all setImmediate callbacks. It is a Node.js-specific API and does not exist in the browser."
Q2: What is the difference between setImmediate and setTimeout(fn, 0)?
"When called from the main module, the execution order is non-deterministic because it depends on whether the minimum 1ms delay for setTimeout has passed by the time the timers phase runs. However, inside an I/O callback, setImmediate ALWAYS runs before setTimeout(fn, 0) because after the poll phase, the event loop goes to the check phase next, before going back to the timers phase in the next iteration."
Q3: What is the difference between setImmediate and process.nextTick?
"process.nextTick callbacks run immediately after the current operation completes — before the event loop continues to the next phase, and even before Promise microtasks. setImmediate runs in the check phase of the event loop — much later. process.nextTick has higher priority than setImmediate. However, overusing process.nextTick can starve the event loop by keeping the nextTick queue full and never letting the event loop advance, which is one reason setImmediate is often preferred for deferring work."
Q4: When would you use setImmediate over process.nextTick?
"Use setImmediate when you want to yield control to the event loop so it can process pending I/O, timers, or other callbacks before your deferred code runs. This is important for keeping a server responsive during CPU-intensive operations. Use process.nextTick when you need to defer something but want it to run as soon as possible — before any I/O. The Node.js docs themselves recommend preferring setImmediate over process.nextTick in most cases because it avoids potential event loop starvation."
Quick Recap
| Concept | Description |
|---|---|
| setImmediate | Runs in the check phase, after poll phase |
| Check Phase | Node.js event loop phase dedicated to setImmediate |
| vs setTimeout(0) | Non-deterministic in main; setImmediate wins inside I/O |
| vs nextTick | nextTick runs BEFORE any event loop phase; setImmediate runs IN check phase |
| clearImmediate | Cancels a scheduled setImmediate callback |
| Best use | Breaking CPU work, emitting events after constructor, preventing stack overflow |
Key Points to Remember
- setImmediate runs in the check phase — right after the poll (I/O) phase
- It is a Node.js-only API — not available in browsers
- Inside I/O callbacks: setImmediate ALWAYS runs before setTimeout(fn, 0)
- From the main module: order with setTimeout is non-deterministic
- process.nextTick > Promise > setImmediate > setTimeout in priority
- Use setImmediate to break up CPU-heavy work and keep server responsive
- Use clearImmediate() to cancel a pending setImmediate callback
- Prefer setImmediate over nextTick to avoid event loop starvation
Keep coding, keep learning! See you in the next one!
Post a Comment