Multi-threaded JavaScript with the Event Loop: Breaking the Single-threaded Barrier

CodeStax.Ai
7 min readMay 5, 2023

What is an event loop in JavaScript ?

An event loop is a mechanism used in JavaScript to handle asynchronous code. It is responsible for managing the order in which tasks are executed and ensuring that no tasks are blocked by other tasks that are taking longer to execute.

The event loop works by continuously monitoring the call stack, which is a data structure that records the execution context of a program. When a function is called, its execution context is added to the top of the call stack. When the function returns, its execution context is removed from the call stack.

If a function is asynchronous, such as a network request or a timer, it is not executed immediately. Instead, it is added to a queue of tasks that are waiting to be executed. The event loop constantly checks this queue for tasks that are ready to be executed. When a task is found, its execution context is added to the call stack and executed.

In summary, the event loop is a key feature of JavaScript that enables the language to handle asynchronous code efficiently. It works by constantly monitoring the call stack and a queue of tasks, ensuring that all tasks are executed in the correct order without blocking the program.

Let’s Understand little bit about the call stack first:

function greet(name) {
console.log("Hello, " + name + "!");
}

function greetTwoPeople() {
greet("Alice");
greet("Bob");
}

greetTwoPeople();

When we run greetTwoPeople(), here’s what happens:

  1. The first function to be called is greetTwoPeople(). This function then calls the greet() function twice, once with the argument “Alice” and once with the argument “Bob”.
  2. When greet(“Alice”) is called, a new frame is added to the call stack for the greet() function with the argument “Alice”. The console.log() statement in greet() is executed, printing “Hello, Alice!” to the console.
  3. When the greet(“Alice”) function call completes, its frame is removed from the call stack.
  4. The greet(“Bob”) function call then adds a new frame to the call stack for the greet() function with the argument “Bob”. The console.log() statement in greet() is executed again, printing “Hello, Bob!” to the console.
  5. When the greet(“Bob”) function call completes, its frame is removed from the call stack.

Finally, when all function calls have completed, the greetTwoPeople() frame is removed from the call stack.

So the final output of this code would be:

Hello, Alice!
Hello, Bob!

This call stack is one of the reasons for slowness. If there is a network call or image processing in the call stack which might take time, it will stop the next processes from executing even if they are small and quick to run.
Let us understand how JavaScript can handle this:

How are callbacks and task-queue connected in the event loop?

┌───────────────────────────────────────────┐
│ +-------------------------------------+ │
│ | JavaScript Application | │
│ +-------------------------------------+ │
│ +-------------------------------------+ │
│ | Event Loop | │
│ +-------------------------------------+ │
│ +----------------+ +----------------+ │
│ | Callback | | Task Queue | │
│ +----------------+ +----------------+ │
│ ▲ │ │
│ └───────────────┬──────────┘ │
│ +----------------+----------------+ │
│ | Asynchronous Operations | │
│ +----------------+----------------+ │
│ │
└───────────────────────────────────────────┘

Explanation:

  • The JavaScript application is the main program that is executed.
  • The Event Loop is responsible for managing the execution of asynchronous code and handling events.
  • Callbacks are functions that are registered to be called later when an asynchronous operation completes.
  • The Task Queue is a queue of tasks (callbacks) that are ready to be executed by the Event Loop.
  • Asynchronous operations are executed by the browser or Node.js, and when they complete, their callbacks are added to the Task Queue.
  • The Event Loop checks the Task Queue for any pending tasks, and if there are any, it executes them in the order they were added.
  • Asynchronous operations can also trigger events, which are added to the Event Queue. The Event Loop will handle these events in a similar way to tasks, by adding their callbacks to the Task Queue when they are ready to be executed.

How does all of this work?

Event loop and task queue working
  • The flow chart shows the main components of the event loop, including the event loop itself, the task queue, and the call stack. The event loop continuously checks for events and tasks, and executes them as they become available.
  • The flow chart also shows two types of tasks: callbacks and microtasks. Callbacks are functions that are queued up to be executed when an event occurs, while microtasks are functions that are queued up to be executed when the call stack is empty.
  • The call stack represents the sequence of functions that are currently being executed. When a new task is added to the queue, it is added to the end of the queue and will be executed after all the functions in the call stack have been executed.
  • When a new task is added to the queue, it is added to the end of the queue and will be executed after all the functions in the call stack have been executed. When the event loop finds a task in the queue, it moves the task from the task queue to the call stack, and the task is executed. Once the task is complete, it is removed from the call stack and the event loop continues to check for new events and tasks.

Here is an example of how it all works with javascript code:

console.log("Start");

setTimeout(function() {
console.log("Callback");
}, 0);

Promise.resolve().then(function() {
console.log("Promise");
});

console.log("End");

In this code, we first log “Start” to the console. Then, we set a timeout with a callback function that logs “Callback” to the console after 0 milliseconds. Next, we create a promise that logs “Promise” to the console when it is resolved. Finally, we log “End” to the console.

Here’s how the event loop, call stack, callback queue, and task queue work together to execute this code:

  • The code is initially executed, and “Start” is logged to the console. This is added to the call stack.
  • setTimeout is called, which schedules a task to run the callback function after 0 milliseconds. This task is added to the task queue.
  • Promise.resolve().then() is called, which schedules a microtask to run the callback function when the promise is resolved. This microtask is added to the microtask queue (also called the job queue).
  • “End” is logged to the console. This is added to the call stack.
  • Since the call stack is empty, the event loop checks if there are any tasks in the task queue or microtasks in the microtask queue. If there are, it adds them to the call stack.
  • The microtask queue is processed first, since it has higher priority than the task queue. The callback function for the promise is added to the call stack, and “Promise” is logged to the console.
  • The call stack is now empty, so the event loop checks the task queue. The callback function for setTimeout is added to the call stack, and “Callback” is logged to the console.
  • The call stack is now empty, so the event loop checks if there are any more tasks in the task queue or microtasks in the microtask queue. Since there are none, the event loop waits for new tasks or microtasks to be added.

So, the output of this code will be:

Start
End
Promise
Callback

As you can see, the event loop, call stack, callback queue, and task queue all work together to ensure that asynchronous tasks are executed in the correct order and at the appropriate time. The call stack is used to execute synchronous code, while the callback queue and task queue are used to schedule and execute asynchronous tasks. The event loop manages the interaction between the call stack and the task and callback queues, ensuring that tasks and microtasks are executed in the correct order and at the appropriate time.

Here is an interesting app create by Philip Roberts where you can visualize all of these processes: click here

About the Author:

Nishant Choudhary is a Software Developer with around 2 years of experience in Software Development where he worked in multiple organizations and contributed to solving different kinds of complex problems with his creativity and enthusiasm for learning and different kinds of skills such as problem-solving, web designing and development, analysis of architecture and development of Progressive Web Applications.

About CodeStax.Ai

At CodeStax.Ai, we stand at the nexus of innovation and enterprise solutions, offering technology partnerships that empower businesses to drive efficiency, innovation, and growth, harnessing the transformative power of no-code platforms and advanced AI integrations.

But the real magic? It’s our tech tribe behind the scenes. If you’ve got a knack for innovation and a passion for redefining the norm, we’ve got the perfect tech playground for you. CodeStax.Ai offers more than a job — it’s a journey into the very heart of what’s next. Join us, and be part of the revolution that’s redefining the enterprise tech landscape.

--

--

CodeStax.Ai

Tech tales from our powerhouse Software Engineering team!