process.nextTick()

Queues a callback to run after the current operation completes, before the event loop continues.

Signature
process.nextTick(callback[, ...args])
  • Added in Node.js v0.1.26

Parameters

ParameterTypeDescription
$callbackFunctionFunction to invoke once the current operation on the JavaScript stack finishes.
$...argsoptionalanyAdditional arguments passed to the callback when it is invoked.

Return value

undefinedNothing; the callback is added to the next tick queue.

Errors

Throws ERR_INVALID_ARG_TYPE if callback is not a function; an error thrown inside the callback surfaces as an uncaught exception.

Changelog

  • PHP v22.7.0Stability changed to Legacy; queueMicrotask() is recommended instead (also in v20.18.0).
  • PHP v18.0.0Passing an invalid callback now throws ERR_INVALID_ARG_TYPE instead of ERR_INVALID_CALLBACK.
  • PHP v1.8.1Additional arguments after callback are supported and forwarded to it.

process.nextTick() adds a callback to the next tick queue, which is drained completely once the current operation on the JavaScript stack finishes — before promise microtasks, and before the event loop moves on to timers or setImmediate. The full ordering from one synchronous block is: synchronous code, then all next-tick callbacks, then promise microtasks, then the event loop.

When to reach for it

The classic use is making an API consistently asynchronous. If a function sometimes calls its callback synchronously and sometimes after I/O, callers get subtly different execution order depending on the path taken. Deferring the synchronous branch by one tick gives every caller the same behaviour, and lets them attach event listeners after a constructor returns but before any event fires.

What trips people up

  • It is now Legacy. Since v22.7.0 (and v20.18.0) the docs mark it Stability 3 and recommend queueMicrotask(), which schedules onto the standard microtask queue and behaves the same in browsers.
  • It can starve the loop. The queue is drained fully, so a callback that schedules another nextTick recursively blocks I/O and timers forever — the process pins a core and never responds.
  • It outranks promises. A nextTick callback registered after a .then() still runs first.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Make a function that can return early still call its callback asynchronously, like the I/O path does.
  • Emit an event from a constructor after the caller has had a chance to attach listeners.
  • Defer error reporting until the current stack unwinds, so the throw is not caught by the caller's try block.
  • Explain or debug ordering when a callback fires earlier than a promise you expected first.

Frequently asked questions

What is the difference between process.nextTick() and setImmediate()?

process.nextTick() runs before the event loop continues, draining its whole queue as soon as the current operation finishes, while setImmediate() runs in the check phase of the next loop iteration after I/O callbacks. Use setImmediate() to yield to the loop and nextTick() only to defer within the current tick.

Does process.nextTick() run before promises?

Yes — the next tick queue is drained completely before promise microtasks, so a nextTick callback registered after a .then() still runs first. Both run before any timer or setImmediate callback.

Is process.nextTick() deprecated?

It is not removed, but since v22.7.0 and v20.18.0 the documentation marks it Stability 3 (Legacy) and recommends queueMicrotask() instead. Existing code keeps working; new code should prefer queueMicrotask() unless it specifically needs to run ahead of the microtask queue.

More examples

Making an API consistently asynchronous

The output shows the bug this pattern prevents. `readBad` fires its callback before the caller finishes setting up, so the callback observes half-initialised state; `readGood` always defers, so both the cached and the uncached path behave identically. Note the extra arguments passed straight to nextTick — supported since v1.8.1 and cheaper than wrapping in a closure.

Example · javascript
const fs = require('node:fs');

const cache = new Map();

// Inconsistent: the cached path calls back synchronously.
function readBad(file, cb) {
  if (cache.has(file)) return cb(null, cache.get(file));
  fs.readFile(file, 'utf8', (err, data) => {
    if (!err) cache.set(file, data);
    cb(err, data);
  });
}

// Consistent: every path calls back on a later tick.
function readGood(file, cb) {
  if (cache.has(file)) return process.nextTick(cb, null, cache.get(file));
  fs.readFile(file, 'utf8', (err, data) => {
    if (!err) cache.set(file, data);
    cb(err, data);
  });
}

cache.set('a.txt', 'cached');
let ready = false;

readBad('a.txt', () => console.log('readBad sees ready =', ready));
readGood('a.txt', () => console.log('readGood sees ready =', ready));
ready = true;
// readBad sees ready = false
// readGood sees ready = true

Starving the event loop

The output shows the bounded version behaving normally — the four extra ticks drain, then the loop proceeds to timers and immediates. The comment describes the unbounded case, which cannot be demonstrated by running it: because the next tick queue is drained to empty before the event loop advances, a self-rescheduling callback means the timer and immediate never fire, incoming connections are never accepted, and the process appears hung at 100% CPU. `setImmediate()` recursion does not have this problem, since it yields once per loop iteration.

Example · javascript
let ticks = 0;

setTimeout(() => console.log('timer fired'), 0);
setImmediate(() => console.log('immediate fired'));

function spin() {
  if (++ticks < 5) process.nextTick(spin);
}
spin();

console.log('scheduled', ticks, 'ticks so far');
// scheduled 1 ticks so far
// timer fired
// immediate fired

// Remove the `ticks < 5` guard and the last two lines never print:
// the next tick queue is drained fully before the loop advances,
// so an unbounded recursion pins the CPU and blocks all I/O forever.

queueMicrotask() as the modern replacement

The output shows that nextTick still runs ahead of the microtask queue, while queueMicrotask and promise callbacks share one queue in registration order. For the usual goal — deferring work until the current stack unwinds — queueMicrotask is now the recommended API: it is standard across browsers and Node, and it cannot indefinitely postpone the event loop the way an unbounded nextTick chain can.

Example · javascript
const order = [];

process.nextTick(() => order.push('nextTick'));
queueMicrotask(() => order.push('queueMicrotask'));
Promise.resolve().then(() => order.push('promise'));

setImmediate(() => console.log(order.join(' -> ')));
// nextTick -> queueMicrotask -> promise

Discussion

  • Be the first to comment on this lesson.