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 interviewssetImmediate!

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

ConceptDescription
setImmediateRuns in the check phase, after poll phase
Check PhaseNode.js event loop phase dedicated to setImmediate
vs setTimeout(0)Non-deterministic in main; setImmediate wins inside I/O
vs nextTicknextTick runs BEFORE any event loop phase; setImmediate runs IN check phase
clearImmediateCancels a scheduled setImmediate callback
Best useBreaking 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!