Marcell Ciszek Druzynski

Js event loop

JavaScript is a single-threaded language, but it can handle multiple tasks concurrently thanks to the event loop. Let's dive into how the event loop works and why it's essential for building performant web applications.

December 27, 2024

Have you ever wondered how JavaScript works and how we can run asynchronous tasks even though JavaScript is a single-threaded language? Today we're going to talk about something that's been playing hard to get with many JavaScript developers - the Event Loop.

JavaScript as a single-threaded language

JavaScript is a single-threaded language, meaning it can only execute one task at a time in its main thread. However, thanks to the event loop, which is part of the JavaScript runtime environment (browser or Node.js), JavaScript can manage tasks more efficiently. While JavaScript does not support true parallel execution in the main thread, the event loop allows it to handle multiple tasks concurrently.

When a task takes time to complete, such as I/O operations or network requests, JavaScript can offload it to Web APIs (in browsers) or C++ APIs (in Node.js), allowing the main thread to continue processing other tasks in the meantime. This makes JavaScript capable of handling concurrent operations, even though it doesn't perform parallel execution in the same way as multithreaded languages.

In simpler terms, JavaScript can juggle multiple tasks by managing when each task runs, but it does not execute them simultaneously on multiple threads.

By contrast, languages like C++ or Rust can create multiple threads to perform true parallel processing, where tasks run simultaneously across multiple CPU cores.

This is important to know since we never want to block the main thread when computing some task. For example, a database call should be run asynchronously without blocking the main thread.

What's This Event Loop Thing?

Every time we call a function, it is added to the call stack. The call stack is a data structure that follows the LIFO (Last In, First Out) principle. This means that the last function added to the stack is the first one to be removed.

When a function is called, it gets pushed onto the top of the stack. If another function is called within it, that new function is also added to the top of the stack. Once a function completes its execution, it is popped off the stack, and the program resumes execution with the function below it. This process continues until the stack is empty.

Imagine you're at a restaurant. You've got:

  • A chef (JavaScript's main thread) who can only cook one meal at a time
  • A waiter (the Event Loop) who manages all the orders
  • A line of customers (the Call Stack) waiting to place their orders
  • A waiting area (the Task Queues) where people hang out until their table is ready

Our chef (JavaScript) is cooking away in the kitchen, handling one order at a time. But what happens when someone orders a pizza that takes 20 minutes to bake? Should everyone else wait? If everyone would wait for the pizza to be done, it would be a disaster. We have blocked the main thread with a heavy task.

1console.log("I want a pizza!");
2setTimeout(() => {
3 console.log("Pizza is ready! 🍕");
4}, 2000);
5console.log("I'll have a salad while waiting...");
1console.log("I want a pizza!");
2setTimeout(() => {
3 console.log("Pizza is ready! 🍕");
4}, 2000);
5console.log("I'll have a salad while waiting...");

Here's what actually happens:

  1. "I want a pizza!" gets shouted out immediately
  2. The pizza order gets sent to the waiting area (Task Queue)
  3. "I'll have a salad while waiting..." gets called right away
  4. After 2 seconds, the Event Loop checks if the chef is free and brings in the "Pizza is ready! 🍕"

So with the event loop, we can put that heavy task on the event queue. While the pizza is in the oven and gets baked, we can proceed with other orders in the meantime, so the customers don't have to wait.

Understanding Task Queues

JavaScript actually has two types of task queues:

  1. Microtask Queue:

    • Handles Promises (.then(), .catch(), .finally())
    • process.nextTick() (in Node.js)
    • Has higher priority than the macrotask queue
    • Executes all microtasks before moving to macrotasks
  2. Macrotask Queue:

    • Handles setTimeout, setInterval
    • DOM events
    • AJAX/Network requests
    • Executes one task at a time, allowing rendering between tasks
1console.log("Script start");
2
3setTimeout(() => {
4 console.log("Timeout"); // Macrotask
5}, 0);
6
7Promise.resolve()
8 .then(() => console.log("Promise 1")) // Microtask
9 .then(() => console.log("Promise 2")); // Microtask
10
11console.log("Script end");
12
13// Output:
14// Script start
15// Script end
16// Promise 1
17// Promise 2
18// Timeout
1console.log("Script start");
2
3setTimeout(() => {
4 console.log("Timeout"); // Macrotask
5}, 0);
6
7Promise.resolve()
8 .then(() => console.log("Promise 1")) // Microtask
9 .then(() => console.log("Promise 2")); // Microtask
10
11console.log("Script end");
12
13// Output:
14// Script start
15// Script end
16// Promise 1
17// Promise 2
18// Timeout

Animation Frame Queue

There's also a special queue for animations using requestAnimationFrame. This API is specifically designed for smooth animations and visual updates:

1requestAnimationFrame(() => {
2 // Update animation
3 element.style.transform = `translateX(${x}px)`;
4});
1requestAnimationFrame(() => {
2 // Update animation
3 element.style.transform = `translateX(${x}px)`;
4});

The browser executes these callbacks before the next repaint, typically targeting 60 frames per second. This ensures smooth animations without blocking the main thread or interfering with other tasks.

Why Should You Care?

Understanding JavaScript's event loop isn't just theoretical - it's essential for building performant web applications.

Event loop knowledge helps you:

  • Debug asynchronous code effectively
  • Keep your UI responsive under load
  • Optimize performance-critical applications
  • Write better concurrent code
  • Understand framework behavior

Without this knowledge, you'll likely create applications that freeze, stutter, or behave unexpectedly under load. The event loop isn't just an implementation detail - it's a core concept that shapes how we write modern JavaScript.

While I have just brought up the event loop and hoping that I perhaps opened your mind to get to know more about how it works, I do recommend the following posts and videos to deepen your knowledge:

Summary

The Event Loop might seem like a complicated relationship at first, but once you understand how it works, it's actually a beautiful thing. It's what allows JavaScript to handle multiple operations without freezing up your browser or server.

Remember: JavaScript isn't slow or bad at multitasking - it just has a very efficient personal assistant helping it manage its schedule!