node.js
asynchronous programming
event loop
execution order
JavaScript

Does node.js preserve asynchronous execution order?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Node.js preserves some orders, but not all of them. Synchronous statements run in program order, promise callbacks run in a defined microtask order, and callbacks queued in the same phase are processed in queue order. What Node.js does not promise is that independent asynchronous operations will finish in the same order they were started.

Start Order Is Not Finish Order

This is the core idea:

  • invocation order is deterministic
  • completion order depends on the event loop, timers, I/O, and scheduling

For example:

javascript
setTimeout(() => console.log('A'), 50);
setTimeout(() => console.log('B'), 10);
console.log('C');

This always prints C first because the console.log call is synchronous. After that, B normally prints before A because its timer expires first.

So Node.js is not preserving the order in which the asynchronous tasks were created. It is preserving the rules of the event loop.

Promise Callbacks Have Their Own Ordering Rules

Microtasks from promises are processed after the current JavaScript turn finishes and before the event loop moves on to many other queued callbacks.

javascript
Promise.resolve().then(() => console.log('promise 1'));
Promise.resolve().then(() => console.log('promise 2'));
console.log('sync');

This prints:

text
sync
promise 1
promise 2

The promise callbacks run after the synchronous code, and two callbacks queued in that microtask queue are processed in order.

That does not mean every asynchronous API in Node has the same behavior as promises. Timers, file I/O, network I/O, and immediates live in different queues and phases.

I/O Completion Order Is Not Guaranteed

Suppose you start two unrelated asynchronous operations:

javascript
1const fs = require('node:fs/promises');
2
3async function run() {
4  const a = fs.readFile('./a.txt', 'utf8');
5  const b = fs.readFile('./b.txt', 'utf8');
6
7  console.log(await a);
8  console.log(await b);
9}
10
11run().catch(console.error);

The reads start concurrently. Either one may finish first internally. The await a and await b lines still force the output order of your program, but that is your code preserving order, not the runtime deciding to complete the operations in creation order.

If you instead log as each promise resolves:

javascript
fs.readFile('./a.txt', 'utf8').then(() => console.log('a done'));
fs.readFile('./b.txt', 'utf8').then(() => console.log('b done'));

either message may appear first.

If You Need Order, Express It

When order matters, write code that makes the dependency explicit.

Sequential:

javascript
1async function sequential() {
2  const first = await fetchValue(1);
3  const second = await fetchValue(2);
4  console.log(first, second);
5}

Concurrent start, ordered use:

javascript
1async function concurrentButOrdered() {
2  const firstPromise = fetchValue(1);
3  const secondPromise = fetchValue(2);
4
5  const first = await firstPromise;
6  const second = await secondPromise;
7  console.log(first, second);
8}

Helper:

javascript
1function fetchValue(id) {
2  return new Promise(resolve => {
3    const delay = id === 1 ? 50 : 10;
4    setTimeout(() => resolve(`value-${id}`), delay);
5  });
6}

In both versions, your control flow defines the visible order.

process.nextTick, Promises, and Timers

Node also has queue priorities that surprise many developers. process.nextTick callbacks run before normal promise microtasks in Node, and both run before most timer and I/O callbacks for the next event-loop turn.

javascript
1setTimeout(() => console.log('timer'), 0);
2Promise.resolve().then(() => console.log('promise'));
3process.nextTick(() => console.log('nextTick'));
4console.log('sync');

A typical result is:

text
1sync
2nextTick
3promise
4timer

That is still deterministic, but it is not "who was written first wins". It is "which queue runs first".

Common Pitfalls

The most common mistake is assuming two asynchronous operations will finish in creation order. They usually will not unless you explicitly serialize them.

Another issue is mixing queue types without understanding their priority. process.nextTick, promise microtasks, timers, and I/O callbacks do not all run in the same phase. Developers also often mistake ordered output from await for ordered completion of the underlying work. Those are different things.

Summary

  • Node.js preserves defined event-loop and microtask rules, not arbitrary async creation order.
  • Independent asynchronous operations can complete in any order.
  • Promise callbacks run in microtask order after the current synchronous work finishes.
  • If output or side effects must occur in order, express that with await, chaining, or explicit sequencing.
  • Learn the difference between timers, I/O callbacks, promise microtasks, and process.nextTick.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.