JavaScript Event Loop

JavaScript operates on a single thread, meaning it can only do one thing at a time. You would think that it would be slow because of this. However, its runtime model is based on an event loop, which allows it to handle thousands of requests on a single thread. The event loop works by invoking the JavaScript engine to execute pieces of the code over time.

In this post, I will go into more depth about how JavaScript works under the hood and how the code gets executed.

Explanation

Imagine you were going through the drive-thru of your favorite fast food restaurant. A car places an order, picks it up at the window, and drives off before another car pulls up. This is straight forward. But how about the event where the first car is still waiting at the window while another car places an order? What if the second car’s order finishes before the first? Will the person working the drive-thru just stop what they’re doing, walk outside, and serve the second customer? No, of course not. The event loop works the same way.

The single thread receives a task to be executed which gets added to a callback queue. The callback queue is a first-in-first-out data structure, meaning the first task placed in the queue will be handled first and the most recent task that was placed in the queue will be handled last.

Then we have what is known as the call stack, where the tasks finally get executed. The call stack is a last-in-first-out data structure that allows the JavaScript engine to keep track of its place in code that calls multiple functions. When a function is called, it gets pushed onto the stack and when a function returns, it is popped off of the stack.

The event loop is constantly monitoring both the call stack and the queue. If the call stack is empty, the event loop pulls the first task from the queue and places it on the stack. If the call stack is busy handling another task, the event loop will wait until the stack is clear to push the next task onto the stack. When the queue is empty and there are no more tasks to be executed, the event loop sleeps until another task is received.

Event Loop Illustration

The JavaScript engine is also composed of a heap, where memory allocation happens. When variables are defined, objects will be stored in a region in memory.

JavaScript Is More Than the Engine

Since the event loop operates within a single thread, it’s important to not block the event loop. Promises, callbacks and async/await can be used to avoid blocking the main thread.

But although the JavaScript engine executes code inside of the main thread, JavaScript is more than just the engine. JavaScript is asynchronous. Now you may be wondering how JavaScript can be single-threaded and asynchronous. The runtime in the browser provides Web APIs (also known as browser APIs) that allow us as the developer to more easily implement features into our web application. If we were talking about the Node.js runtime, then instead of Web APIs, we would would have C++ APIs. Web APIs are asynchronous, and will not block the event loop.

If you were to look at the source code for a JavaScript engine, like Google Chrome’s V8 engine, you wouldn’t find things like Fetch API, setTimeout(), console. These are are all part of the Web API provided by the browser.

Some of the functionality provided by browser Web APIs:

  • Ability to manipulate audio and add effects and filters
  • Speech recognition
  • Bluetooth communication
  • Adding a payment method to an application
  • Ability to access mouse input, coordinates, action, movement

Code Example

Let’s say you were working with some code that had a loop that called setTimeout() each time:

Example 1

When I first saw this code, my expectation was that 0, 1, 2, and 3 would output to the console. I did not think that setTimeout() would have any impact on the result. However, when this code is ran, 4 outputs to the console each time.

It turns out that you can wrap a long-running function in setTimeout() so that the Web API will handle it asynchronously:
Wrap Function In setTimeout()
It’s easy to think that the callback will fire immediately when passing zero milliseconds as a parameter. That is not the case, however. Here is what is happening:

  1. setTimeout() is called by the call stack. Window.setTimeout() is a Web API, so the corresponding Web APIs will be called.
  2.  After 0 milliseconds, the callback function (an anonymous function at this point) will be placed onto the callback queue (not the call stack). Even if the queue is empty and the thread of execution is not busy, the callback will not execute immediately, but after about a 10 millisecond delay when the tab has focus.
  3. The other setTimeout() calls will be handled by the call stack, and each of the corresponding callback functions will be added to the callback queue.
  4. By the time the first callback is executed by the call stack, the for loop has already finished so i === 4 which is why it gets printed four times.

So next time you are struggling to find a solution to a long-running task bottle-necking your application, try putting it in a setTimeout() to be handled asynchronously.

Conclusion

Hopefully this post has deepened your understanding of how JavaScript works under the hood. Not understanding the event loop and the single-threaded nature of JavaScript can lead to some long and painful debugging sessions trying to figure out why your application is not working as expected because some long-running task is blocking the main thread.

And if you’re trying to get a job as a JavaScript developer, there’s a chance something will be asked that will require knowledge on how the event loop works to answer.

Success depends upon previous preparation, and without such preparation, there is sure to be failure. — Confucius

If you found this post helpful, subscribe to my blog so you won’t miss future content and share it with a JavaScript developer you know!