Single Threaded nature of JS Runtime

Sunilkathuria
In Computing World
Published in
6 min readMar 27, 2024

As we all know, JavaScript is a single-threaded application. Yet, it provides a mechanism to execute code asynchronously and gives the impression of parallel execution. This article will teach us various concepts that help us understand how JavaScript works asynchronously. The concepts we will cover in this article are synchronous and asynchronous execution, call stack, callback queue, and EventLoop.

Synchronous code execution

In synchronous code execution, the statements execute one after the other in a sequence. While executing a statement, the subsequent statement waits for the execution to complete. It is very easy to understand.

Consider the following code snippet. The three functions execute in a sequence.

The call stack looks like the following.

Refer to the other example below. In this case, each function calls another function in a sequence until the output is displayed on the console. With each call, the stack keeps filling up. Once the information is displayed, each function starts popping out of the stack in reverse order. This, too, is an example of a synchronous call.

Asynchronous code execution

In asynchronous code execution, one or more tasks start and process independent of the flow of the main program. The subsequent task does not wait for the current task to finish. These tasks do not block the sequence of execution. These are generally time-consuming tasks like timers, I/O operations, network requests, etc.

JavaScript provides a mechanism for asynchronously executing time-consuming tasks without making an application unresponsive. One mechanism we will discuss in this article is the function setTimeout.

Other mechanisms that help with the asynchronous execution are setInterval, promises, async/await and Event Listeners.

Callbacks are used for asynchronous execution. They pass a function as an argument to another function, which is called when an asynchronous call completes.

Function setTimeout

The function setTimeout helps to schedule the execution of a function or a code block after a certain period, measured in milliseconds. This scheduling is asynchronous, which means the subsequent code continues to execute. JavaScript does not wait to finish executing scheduled functions or code blocks.

The following code snippet will help us understand the use of asynchronous calls.

The function showMessageWithDelay is called asynchronously with a delay of 3 seconds. This delay is introduced with the help of the function setTimeout. In this case, JavaScript immediately steps to the following statement to execute. To do so, JavaScript pushes the function call showMessageWithDelay out of the call stack and puts the function call showMessageNow(”End”) at the top of the stack for execution. Once the suggested delay of 3 seconds is over, the function showMessageWithDelay appears back on the stack and executes.

Where does the function showMessageWithDelay go from the stack, and how does this appear back on the stack once the suggested delay is over?

Callback queue

The JavaScript runtime maintains a callback queue. It is a First-in-first-out (FIFO) queue. This queue holds the callback functions that are waiting for execution.

In the above example, when the JavaScript runtime calls the function showMessageWithDelay, it is put in the call stack and starts the execution. During the execution, the moment the setTimeout function executes, the function showMessageNow is put in the callback queue with a delay of 3000 milliseconds, and the function showMessageWithDelay continues with the execution. The following animation depicts the flow.

Now, how does this function movement showMessagNow happen from the callback queue to the stack?

This is a simplified view of the Event loop to explain how the setTimeout works. At this point, other mechanisms of asynchronous execution are not discussed.

Event Loop

As the name suggests, the event loop starts the moment the runtime environment is initiated to execute a JavaScript file. It continues to execute until there are no events left for processing.

The purpose of the event loop is to keep track of all the tasks in the stack and callback queue. The event loop monitors task(s) in the callback queue, and when a task is ready for execution (it has timed out), it checks whether the stack is empty. If it is, the task is pulled from the callback queue and pushed into the stack for execution.

Note: the time duration mentioned in the setTimeout function ensures that the function, at minimum, will time out for the specified duration. It may timeout even further while waiting for the call stack to be empty.

More about the Event loop

The above section has simplified how the Call stack, event loop, and callback queue work together. And this is just one “phase” of the whole event loop. Following is a short description of all the phases of an Event loop

Timers. This manages callbacks scheduled by setTimeout and setInterval.

Pending Callbacks. This maintains a queue of callbacks from I/O operations. These callbacks are ready but cannot be immediately executed as another task is in progress.

Idle. This is used for internal housekeeping.

Poll. In this phase, the event loop waits for the I/O events to become available. The I/O event has been completed, and its corresponding callback operation is ready to start. It also addresses the callbacks scheduled by the function setImmediate.

Check: In this phase, the callbacks initiated by setImmediate are invoked here.

Close. This phase is used to manage callbacks related to closing events.

MicroTask Queue

Although these two are not mentioned along with the Event loop, these are two “high priority” queues that are part of the event loop.

The Promise queue is maintained to address small, high-priority jobs. These tasks take precedence over the tasks in various phases of the event loop. This queue is checked whenever the event queue moves from one phase to another. Promise callbacks (.then, .catch, and .finally) and function queueMicrotask are added here.

A Nexttick queue is another queue that fits between the different phases of the event loop and the promise queue. Callbacks initiated by the process.nextTick are handled here. And this gets priority over the promise queue.

The figure below shows a more comprehensive image of the event loop in Node.js

Conclusion

In this article, we learned about synchronous and asynchronous execution.

How an event loop manages the execution of the asynchronous callbacks

Introduced to the various phases of an event loop and microtask queue.

References

A complete guide to the Node.js event loop

The Node.js Event loop

A Deep Dive Into the Node js Event Loop — Tyler Hawkins

NodeJS — an interesting case where Microtasks runs before process.nextTick

--

--