How does the single-threaded javascript manage to do multiple tasks together?

Shubham Sharma
7 min readDec 12, 2021

--

We know of javascript as a single-threaded, asynchronous, concurrent and non-blocking language. But the first question which arises after reading this is that how can a language be single-threaded ie able to do one task at a time and yet be asynchronous, concurrent and non-blocking. Single-threaded implies that javascript has one call stack which stores the sub-routines or functions called. However, just like any stack, only the function at the top of the call stack can be executed at any point of time. And that function can be time taking. It might involve a network call, running a while loop for 1,000,000 iterations or taking some input from the user. Anything which takes long, if present in the call stack, will block the execution of everything apart from it. Javascript is used on the browser. Thus, it means that if something like a network request is made on the browser, then the user will not be able to click a button or fill a form on the browser. But that is not what users generally experience on the web. Users are able to listen to music, watch a video and upload or download files simultaneously on the web browser.

To understand this phenomenon, we have to deep dive into the internal workings of javascript. When you execute a script, the JavaScript engine creates a Global Execution Context (GEC) and pushes it on top of the call stack. Whenever a function is called, the JavaScript engine creates a Function Execution Context (FEC) for the function, pushes it on top of the Call Stack, and starts executing the function.

Let’s understand first how the call stack works with only synchronous code. In the infographics, the GEC and FEC are deliberately omitted to reduce clutter. Initially, the call stack will be empty.

1. Empty call stack

First of all, console.log(‘Begin’) will be pushed inside the call stack.

2. Call stack

Since, console.log(‘Begin’) is a non-blocking I/O operation, it will immediately print Begin on the console. Thus, this item in the stack will be popped out after the execution. Then, the call stack again becomes empty as shown in 3.

3. Empty call stack

Then the next command or function to be executed is pushed into the call stack ie saySomething() in this case.

4. Call stack

It further calls printHello(), so that will be pushed into the stack

5. Call stack

Then, console.log(‘Hello’) will be pushed into the stack as that is contained inside printHello().

6. Call stack

Now hello will be printed and console.log(‘Hello’) will be popped out of the call stack

7. Call stack

Then, printHello() will be popped out of the call stack.

8. Call stack

Finally, saySomething() will be popped out of the call stack.

If one does a grep on the v8 source code which is the runtime for javascript in the browser, one will not find anything like setTimeout(), DOM or XMLHttpRequest. It is surprising because one of the first things an newbie learns about javascript is exactly these. Then, how is one able to use setTimeout in the browser or make a network request from the browser.

The answer to this is Web Apis. Web Apis are not part of the javascript language but are built on the core of javascript to give you extra functionality. They interact with the operating system’s async handlers to perform the async operations while abstracting the complexities from the javascript runtime ie v8. If one is using Node.js, web apis get replaced with libuv library written in C++. This library interacts with the operating system’s async handlers in that case.

But the next question is that once the web apis complete the long task, how do they return the data back to the user. They cannot pop the data in the browser at any moment of time as it might be out of context. For accomplishing this, an event loop and a callback queue are used. Callback queue, also known as the Task queue, is a FIFO queue which stores those async tasks which are completed and are ready to be pushed into the call stack. Event loop continuously checks if the call stack is empty, and if it is then it pushes the first element of the callback queue on the call stack.

We can understand this in more detail through an example.
Let’s assume that we have a piece of code which involves an async function printDelayedHi(). Initially, the call stack is empty.

Step 1

Now the control flows to the first statement of code ie console.log(‘Begin’). It is pushed in the call stack as in Step 2, executed and the string Begin is printed on the console after it is popped out as shown in Step 3.

Step 2
Step 3

Then, the function printDelayedHi() is pushed into the call stack as in Step 4.

Step 4

In Step 5, the underlying code inside printDelayedHi() which is a setTimeout(() => {console.log(‘Hi’)}, 3000) is pushed into the stack. Since, it is an asynchronous function, it will be handled by the Web APIs. Thus, this snippet is popped out of the call stack and given to Web APIs as in Step 6.

Step 5

The setTimeout snippet will take 3 seconds in the Web APIs. Meanwhile, printDelayedHi() will be popped out of the call stack as in Step 7.

Step 6
Step 7

Then, console.log(‘End’) is pushed into the call stack as in Step 8.

Step 8

It gets executed immediately and printed on the console as in Step 9. And the call stack becomes empty again.

Step 9

Now, once 3 seconds elapse, console.log(‘Hi’) is taken out from the Web APIs and pushed in the callback queue as in Step 10.

Step 10

Then comes the role of the event loop. The event loop, checks if the call stack is empty and if it is, then it puts the front element of callback queue into the stack as in Step 11. Please note, that if there were something already inside the call stack, then the callback queue will not be dequeued and nothing will be pushed in the call stack.

Step 11

Finally, as in Step 12, the top element of call stack ie console.log(‘Hi”) is popped out of the stack and displayed on the console.

Step 12

In this way, javascript managed to run a setTimeout function which is an asynchronous function despite being single-threaded and having only one call stack. In case of browser, it uses Web APIs and in case of Node js, it uses libuv library. Apart from that it uses a callback queue and an event loop. Javascript is a powerful language which can handle user interface level logic as well as can run web servers. It is quite easy to learn and this makes it one of the most popular languages of the world. I hope this article helped you understand the inner working of javascript while it operates in your browser as well as on your server.

--

--