9/18 | Validate feature ideas earlier with AI-driven prototypes

What are best AI tools? Take the State of AI survey

Builder.io
Builder.io
Contact sales

9/18 | Validate feature ideas earlier with AI-driven prototypes

What are best AI tools? Take the State of AI survey

Builder.io
Builder.io
< Back to blog

Web Development

Visualizing I/O Polling in the Node.js Event Loop

April 11, 2023

Written By Vishwas Gopinath

Welcome to the fifth article in our series on visualizing the Node.js event loop. In the previous article, we explored the I/O Queue and its order of priority when executing asynchronous code. In this article, we will continue to focus on the I/O queue while gradually introducing the check queue as well. There is an important point that needs to be noted, which I will explain in our next experiment.

Enqueueing callback functions

Before we proceed with the experiment, I want to mention that to enqueue a callback function in the check queue, we use the built-in setImmediate() function. The syntax is straightforward: setImmediate(callbackFn). When this function is executed on the call stack, the callback function will be enqueued in the check queue.

The first eight experiments deal with the microtask, timer and I/O queues and have been covered in the previous articles. All experiments are run using the CommonJS module format.

Experiment 9

Code

The code snippet continues from the previous experiment. It includes a call to readFile(), which queues up the callback function in the I/O queue, a call to process.nextTick(), which queues up the callback function in the nextTick queue, a call to Promise.resolve().then(), which queues up the callback function in the promise queue, and a call to setTimeout(), which queues up the callback function in the timer queue.

The setImmediate() call, introduced in this experiment, queues up the callback function in the check queue. To avoid the timer issue from experiment 7, a long-running for loop ensures that when control enters the timer queue, the setTimeout() timer has elapsed and the callback is ready to be executed.

Visualization

If you run the code snippet, you may notice that the output is not what you expected. The callback message from setImmediate() is logged before the callback message from readFile() . Here is the output for reference.

This may seem odd, as the I/O queue appears before the check queue, but it makes sense once we understand the concept of I/O polling that occurs between the two queues. To help illustrate this concept, let me provide a visualization.

First, all functions are executed on the call stack, resulting in callbacks being queued up in the appropriate queues. However, the readFile() callback is not queued up at the same time. Let me explain why.

When the control enters the event loop, the microtask queues are checked first for callbacks. In this case, there is one callback in each of the nextTick queue and the promise queue. The nextTick queue has priority, so we see "nextTick 1" logged first, followed by "Promise 1".

Both queues are empty, and control moves to the timer queue. There is one callback, which logs "setTimeout 1" to the console.

Now comes the interesting part. When the control reaches the I/O queue, we expect the readFile() callback to be present, right? After all, we have a long-running for loop, and readFile() should have completed by now.

However, in reality, the event loop has to poll to check if I/O operations are complete, and it only queues up completed operation callbacks. This means that when the control enters the I/O queue for the first time, the queue is still empty.

The control then proceeds to the polling part of the event loop, where it checks with readFile() if the task has been completed. readFile() confirms that it has, and the event loop now adds the associated callback function to the I/O queue. However, the execution has already moved past the I/O queue, and the callback has to wait for its turn to be executed.

The control then proceeds to the check queue, where it finds one callback. It logs "setImmediate 1" to the console and then starts a new iteration because there is nothing else left to process in the current iteration of the event loop.

It appears that the microtask and timer queues are empty, but there is a callback in the I/O queue. The callback is executed, and "readFile 1" is finally logged to the console.

This is why we see "setImmediate 1" logged before "readFile 1". This behavior actually occurred in our previous experiment as well, but we didn't have any further code to run, so we didn't observe it.

Inference

I/O events are polled and callback functions are added to the I/O queue only after the I/O is complete

Conclusion

Once an I/O operation completes, its callback function is not immediately queued into the I/O queue. Instead, an I/O polling phase checks for the completion of I/O operations and queues up the callbacks for completed operations. This can sometimes result in check queue callbacks being executed before I/O queue callbacks.

However, when both queues contain callback functions, the callbacks in the I/O queue always take priority and run first. It is crucial to understand this behaviour when designing systems that rely on I/O callbacks to ensure the proper ordering and execution of callbacks.

In This Series

Share

Twitter
LinkedIn
Facebook
Share this blog
Copy icon
Twitter "X" icon
LinkedIn icon
Facebook icon

Visually edit your codebase with AI

Using simple prompts or Figma-like controls.

Try it nowGet a demo

Design to Code Automation

A pragmatic guide for engineering leaders and development teams


Continue Reading
design9 MIN
How to generate (actually good) designs with AI
September 17, 2025
AI9 MIN
7 Levels of Context Engineering for Designers
September 16, 2025
Design to Code8 MIN
Git Branching for Designers
September 11, 2025

Product

Visual CMS

Theme Studio for Shopify

Sign up

Login

Featured Integrations

React

Angular

Next.js

Gatsby

Resources

User Guides

Developer Docs

Forum

Blog

Github

Get In Touch

Chat With Us

Twitter

Linkedin

Careers

© 2020 Builder.io, Inc.

Security

Privacy Policy

Terms of Service

Get the latest from Builder.io

By submitting, you agree to our Privacy Policy

  • Fusion

  • Publish

  • Product Updates

  • Design to Code

  • Headless CMS

    Multi-Brand CMS

  • Landing Pages

  • Web Apps

  • Prototypes

  • Marketing Sites

  • Headless Commerce

  • Documentation

  • Fusion Docs

  • Publish Docs

  • Blog

  • Webinars

  • Guides

  • Case Studies

  • Community Forum

  • Partners

  • Affiliate Program

  • CMS Integrations

  • CMS Blueprints

  • Glossary

  • Figma to Code Guide

  • Headless CMS Guide

  • Headless Commerce Guide

  • Composable DXP Guide

  • About

  • Careers

  • Contact Sales

Security

Privacy Policy

SaaS Terms

Compliance

Cookie Preferences

YouTube icon
Github icon
Blsky Icon
Twitter "X" icon
LinkedIn icon
Feed Icon
Gartner Cool Vendor 2024